diff --git a/coord.go b/coord.go new file mode 100644 index 0000000..53787a2 --- /dev/null +++ b/coord.go @@ -0,0 +1,46 @@ +/* +Copyright © Brian Buller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package widgets + +import "github.com/gdamore/tcell" + +type Coord struct { + X, Y int +} + +func (p Coord) Add(o Coord) Coord { + return Coord{ + X: p.X + o.X, + Y: p.Y + o.Y, + } +} + +func (p Coord) ResizeEvent() *tcell.EventResize { + return tcell.NewEventResize(p.X, p.Y) +} + +func (p Coord) DrawOffset(w Widget, screen tcell.Screen) { + pr := w.GetPos() + w.SetPos(p.Add(pr)) + w.Draw(screen) + w.SetPos(pr) +} diff --git a/example/example b/example/example new file mode 100755 index 0000000..e35ba99 Binary files /dev/null and b/example/example differ diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..5d6f62f --- /dev/null +++ b/example/main.go @@ -0,0 +1,34 @@ +/* +Copyright © Brian Buller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package main + +func main() { + app := NewUi() + defer func() { + maybePanic := recover() + app.cleanup() + if maybePanic != nil { + panic(maybePanic) + } + }() + app.run() +} diff --git a/example/ui.go b/example/ui.go new file mode 100644 index 0000000..a4d03a3 --- /dev/null +++ b/example/ui.go @@ -0,0 +1,118 @@ +/* +Copyright © Brian Buller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package main + +import ( + "fmt" + "os" + + "github.com/gdamore/tcell" +) + +type Ui struct { + h, w int + + running bool + tScreen tcell.Screen + style tcell.Style + + screen *UiScreen + + cursor int +} + +func NewUi() *Ui { + a := Ui{} + err := a.init() + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + return &a +} + +func (ui *Ui) init() error { + var err error + if ui.tScreen, err = tcell.NewScreen(); err != nil { + return err + } + if err = ui.tScreen.Init(); err != nil { + return err + } + + ui.style = tcell.StyleDefault + ui.tScreen.SetStyle(ui.style) + ui.tScreen.Clear() + + ui.SetScreen(&UiScreen{}) + + return nil +} + +func (ui *Ui) run() error { + ui.running = true + + for { + if !ui.running { + return nil + } + + ui.tScreen.Clear() + ui.screen.Draw() + + ui.tScreen.Show() + + // Poll Events + ev := ui.tScreen.PollEvent() + switch ev := ev.(type) { + case *tcell.EventResize: + ui.tScreen.Sync() + ui.w, ui.h = ev.Size() + ui.handleResize(ev) + + case *tcell.EventKey: + if ev.Key() == tcell.KeyCtrlC { + ui.stop() + return nil + } + ui.screen.HandleKey(ev) + + case *tcell.EventTime: + ui.screen.HandleTime(ev) + } + } +} + +func (ui *Ui) handleResize(ev *tcell.EventResize) { + ui.w, ui.h = ev.Size() + ui.screen.HandleResize(ev) +} + +func (ui *Ui) GetSize() (int, int) { return ui.tScreen.Size() } +func (ui *Ui) SetScreen(scr *UiScreen) { + ui.screen = scr + ui.screen.Init(ui) + ui.screen.HandleResize(tcell.NewEventResize(ui.GetSize())) +} + +func (ui *Ui) stop() { ui.running = false } +func (ui *Ui) cleanup() { ui.tScreen.Fini() } diff --git a/example/ui_screen.go b/example/ui_screen.go new file mode 100644 index 0000000..2c80743 --- /dev/null +++ b/example/ui_screen.go @@ -0,0 +1,129 @@ +/* +Copyright © Brian Buller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package main + +import ( + "fmt" + + w "git.bullercodeworks.com/brian/tcell-widgets" + "github.com/gdamore/tcell" +) + +type UiScreen struct { + ui *Ui + w, h int + + widget w.Widget + log *w.Cli + + widgets []w.Widget + cursor int +} + +func (s *UiScreen) Init(ui *Ui) { + // Set up our log viewer + s.ui = ui + s.log = w.NewCli("log", ui.style) + + ll := w.NewLinearLayout("test", ui.style) + ll.SetTabbable(true) + ll.SetLogger(s.log.Log) + + testList := w.NewList("test-list", ui.style) + for i := 0; i < 10; i++ { + testList.Add(fmt.Sprintf("Item %d", i)) + } + testList.SetLogger(s.log.Log) + ll.Add(testList) + + btnL := w.NewLinearLayout("btn-ll", ui.style) + btnL.SetTabbable(true) + btnL.SetLogger(s.log.Log) + btnL.SetOrientation(w.LinLayH) + + btnCancel := w.NewButton("btn-cancel", ui.style) + btnCancel.SetLabel("Cancel") + btnL.Add(btnCancel) + btnL.AddFlag(btnCancel, w.LFAlignVBottom) + + btnOk := w.NewButton("btn-ok", ui.style) + btnOk.SetLabel("Ok") + btnL.Add(btnOk) + btnL.AddFlag(btnOk, w.LFAlignVBottom) + ll.Add(btnL) + ll.AddFlag(btnL, w.LFAlignVBottom) + + dw := w.NewDebugWidget("debug", ui.style) + dw.SetWidget(ll) + dw.SetSize(w.Coord{X: 20, Y: 30}) + dw.SetActive(true) + s.widget = dw + + s.widgets = append(s.widgets, dw) + s.widgets = append(s.widgets, s.log) +} + +func (s *UiScreen) HandleResize(ev *tcell.EventResize) { + s.w, s.h = ev.Size() + s.widget.HandleResize(ev) + lgH := 20 + s.log.SetPos(w.Coord{X: 0, Y: s.h - lgH - 1}) + s.log.HandleResize(w.Coord{X: s.w, Y: lgH}.ResizeEvent()) +} + +func (s *UiScreen) HandleKey(ev *tcell.EventKey) bool { + if ev.Key() == tcell.KeyCtrlJ { + // Ctrl+J is the keypad 'Enter' + ev = tcell.NewEventKey(tcell.KeyEnter, 0, 0) + } + + if s.cursor < len(s.widgets) { + if s.widgets[s.cursor].HandleKey(ev) { + return true + } + } + + if ev.Key() == tcell.KeyTab { + s.cursor = (s.cursor + 1) % len(s.widgets) + if s.cursor == 0 { + s.widget.SetActive(true) + s.log.SetActive(false) + } else { + s.widget.SetActive(false) + s.log.SetActive(true) + } + return true + } + if s.cursor == 0 { + return s.widget.HandleKey(ev) + } else { + return s.log.HandleKey(ev) + } +} +func (s *UiScreen) HandleTime(ev *tcell.EventTime) {} +func (s *UiScreen) Draw() { + s.widget.Draw(s.ui.tScreen) + s.log.Draw(s.ui.tScreen) +} +func (s *UiScreen) Log(txt string, args ...any) {} + +func (s *UiScreen) Exit() {} diff --git a/absolute_layout.go b/wdgt_absolute_layout.go similarity index 96% rename from absolute_layout.go rename to wdgt_absolute_layout.go index c46af6a..8f5cc66 100644 --- a/absolute_layout.go +++ b/wdgt_absolute_layout.go @@ -22,7 +22,6 @@ THE SOFTWARE. package widgets import ( - wh "git.bullercodeworks.com/brian/tcell-widgets/helpers" "github.com/gdamore/tcell" ) @@ -86,8 +85,8 @@ func (w *AbsoluteLayout) Init(id string, s tcell.Style) { func (w *AbsoluteLayout) Id() string { return w.id } func (w *AbsoluteLayout) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) w.updateWidgetLayouts() } @@ -140,16 +139,10 @@ func (w *AbsoluteLayout) Draw(screen tcell.Screen) { } p := w.GetPos() for _, wd := range w.widgets { - wd.DrawOffset(p, screen) + p.DrawOffset(wd, screen) } } -func (w *AbsoluteLayout) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *AbsoluteLayout) Active() bool { return w.active } func (w *AbsoluteLayout) SetActive(a bool) { w.active = a } func (w *AbsoluteLayout) Visible() bool { return w.visible } diff --git a/alert.go b/wdgt_alert.go similarity index 95% rename from alert.go rename to wdgt_alert.go index 2eb8b48..1199670 100644 --- a/alert.go +++ b/wdgt_alert.go @@ -90,12 +90,9 @@ func (w *Alert) Init(id string, style tcell.Style) { func (w *Alert) Id() string { return w.id } func (w *Alert) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) - + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) // Trim space for the borders and pass on the size to the layout - w.layout.SetPos(Coord{X: 1, Y: 1}) w.layout.HandleResize(tcell.NewEventResize(w.w-2, w.h-2)) } @@ -116,14 +113,7 @@ func (w *Alert) Draw(screen tcell.Screen) { } wh.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, wh.BRD_SIMPLE, w.style, screen) - w.layout.DrawOffset(w.GetPos(), screen) -} - -func (w *Alert) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) + w.GetPos().DrawOffset(w, screen) } func (w *Alert) Active() bool { return w.active } diff --git a/bordered_widget.go b/wdgt_bordered.go similarity index 94% rename from bordered_widget.go rename to wdgt_bordered.go index 62b4b5a..ae4f173 100644 --- a/bordered_widget.go +++ b/wdgt_bordered.go @@ -73,8 +73,8 @@ func (w *BorderedWidget) Id() string { return w.id } func (w *BorderedWidget) HandleResize(ev *tcell.EventResize) { // Trim space for border and pass the resize to the widget w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) w.widget.HandleResize(tcell.NewEventResize(w.w-2, w.h-2)) } @@ -97,15 +97,9 @@ func (w *BorderedWidget) Draw(screen tcell.Screen) { wh.BorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.border, w.style, screen) } w.widget.SetPos(Coord{X: w.x + 1, Y: w.y + 1}) - w.widget.DrawOffset(w.GetPos(), screen) + w.GetPos().DrawOffset(w, screen) } -func (w *BorderedWidget) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *BorderedWidget) Active() bool { return w.active } func (w *BorderedWidget) SetActive(a bool) { w.active = a } func (w *BorderedWidget) Visible() bool { return w.visible } diff --git a/button.go b/wdgt_button.go similarity index 96% rename from button.go rename to wdgt_button.go index 89c5512..a84ed29 100644 --- a/button.go +++ b/wdgt_button.go @@ -61,8 +61,8 @@ func (w *Button) Init(id string, style tcell.Style) { func (w *Button) Id() string { return w.id } func (w *Button) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) } func (w *Button) HandleKey(ev *tcell.EventKey) bool { @@ -118,12 +118,6 @@ func (w *Button) Draw(screen tcell.Screen) { wh.DrawText(w.x, w.y+2, fmt.Sprintf("╰%s╯", strings.Repeat("─", w.w-2)), dStyle, screen) } -func (w *Button) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *Button) Active() bool { return w.active } func (w *Button) SetActive(a bool) { w.active = a } func (w *Button) Visible() bool { return w.visible } diff --git a/chat.go b/wdgt_chat.go similarity index 97% rename from chat.go rename to wdgt_chat.go index 8c8dcfa..ad79c95 100644 --- a/chat.go +++ b/wdgt_chat.go @@ -72,8 +72,8 @@ func (w *Chat) Init(id string, s tcell.Style) { func (w *Chat) Id() string { return w.id } func (w *Chat) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) } func (w *Chat) HandleKey(ev *tcell.EventKey) bool { @@ -157,13 +157,6 @@ func (w *Chat) Draw(screen tcell.Screen) { // x += len(post) - 1 } -func (w *Chat) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} - func (w *Chat) Active() bool { return w.active } func (w *Chat) SetActive(a bool) { w.active = a } func (w *Chat) Visible() bool { return w.visible } diff --git a/checkbox.go b/wdgt_checkbox.go similarity index 95% rename from checkbox.go rename to wdgt_checkbox.go index 1351cb1..29d6a6a 100644 --- a/checkbox.go +++ b/wdgt_checkbox.go @@ -66,8 +66,8 @@ func (w *Checkbox) Init(id string, style tcell.Style) { func (w *Checkbox) Id() string { return w.id } func (w *Checkbox) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) } func (w *Checkbox) HandleKey(ev *tcell.EventKey) bool { @@ -96,12 +96,6 @@ func (w *Checkbox) Draw(screen tcell.Screen) { wh.DrawText(w.x, w.y, fmt.Sprintf("[%s] %s", string(w.state), w.label), dStyle, screen) } -func (w *Checkbox) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *Checkbox) Active() bool { return w.active } func (w *Checkbox) SetActive(a bool) { w.active = a } func (w *Checkbox) Visible() bool { return w.visible } diff --git a/cli.go b/wdgt_cli.go similarity index 88% rename from cli.go rename to wdgt_cli.go index 6433aaf..bd73be2 100644 --- a/cli.go +++ b/wdgt_cli.go @@ -74,8 +74,11 @@ func (w *Cli) Init(id string, s tcell.Style) { w.tabbable = true } -func (w *Cli) Id() string { return w.id } -func (w *Cli) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() } +func (w *Cli) Id() string { return w.id } +func (w *Cli) HandleResize(ev *tcell.EventResize) { + w.w, w.h = ev.Size() +} + func (w *Cli) HandleKey(ev *tcell.EventKey) bool { if !w.active { return false @@ -129,12 +132,18 @@ func (w *Cli) Draw(screen tcell.Screen) { x, y := w.x+1, w.y+1+w.h-3 for i := 0; i < w.h-2; i++ { - if len(w.log) > i { - line := w.log[len(w.log)-i-1] + if len(w.log) > (i + w.logPosition) { + idx := len(w.log) - (i + w.logPosition) - 1 + if idx < 0 { + y-- + wh.DrawText(x, y, wh.PadR("", w.w-2), dStyle, screen) + continue + } + line := w.log[idx] if len(line) > w.w-2 { line = line[:w.w-2] } - wh.DrawText(x, y, wh.PadR(line, w.w), dStyle, screen) + wh.DrawText(x, y, wh.PadR(line, w.w-2), dStyle, screen) y-- } } @@ -153,17 +162,11 @@ func (w *Cli) Draw(screen tcell.Screen) { x += len(pre) wh.DrawText(x, y, cursor, dStyle.Reverse(w.active), screen) x += 1 - wh.DrawText(x, y, wh.PadR(post, w.w-x), dStyle, screen) + wh.DrawText(x, y, wh.PadR(post, w.w-x-2), dStyle, screen) + // wh.DrawText(w.x, y+1, fmt.Sprintf("Index: %d", w.historyPosition), dStyle, screen) // x += len(post) - 1 } -func (w *Cli) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} - func (w *Cli) Active() bool { return w.active } func (w *Cli) SetActive(a bool) { w.active = a } func (w *Cli) Visible() bool { return w.visible } @@ -259,6 +262,26 @@ func (w *Cli) initKeyMap() { } return true }, + tcell.KeyPgUp: func(ev *tcell.EventKey) bool { + if w.historyPosition < len(w.log)-w.h-2 { + w.historyPosition += (w.h - 2) + if w.historyPosition > len(w.log) { + w.historyPosition = len(w.log) + } + return true + } + return false + }, + tcell.KeyPgDn: func(ev *tcell.EventKey) bool { + if w.historyPosition > 0 { + w.historyPosition -= (w.h - 2) + if w.historyPosition < 0 { + w.historyPosition = 0 + } + return true + } + return false + }, }) } func (w *Cli) SetTitle(ttl string) { w.title = ttl } diff --git a/wdgt_debug.go b/wdgt_debug.go new file mode 100644 index 0000000..30a0202 --- /dev/null +++ b/wdgt_debug.go @@ -0,0 +1,203 @@ +/* +Copyright © Brian Buller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package widgets + +import ( + "fmt" + + wh "git.bullercodeworks.com/brian/tcell-widgets/helpers" + "github.com/gdamore/tcell" +) + +// TODO: Make sure this works right... I don't think it does. +type DebugWidget struct { + id string + style tcell.Style + + x, y int + w, h int + setW, setH int + widget Widget + + drawRulers bool + active bool + visible bool + tabbable bool + mTL, mBR Coord // Margins (Top-Right & Bottom Left) + + logger func(string, ...any) +} + +var _ Widget = (*DebugWidget)(nil) + +func NewDebugWidget(id string, s tcell.Style) *DebugWidget { + ret := &DebugWidget{} + ret.Init(id, s) + return ret +} + +func (w *DebugWidget) Init(id string, s tcell.Style) { + w.id = id + w.style = s + w.visible = true + w.tabbable = true + w.drawRulers = true + w.setW, w.setH = -1, -1 +} + +func (w *DebugWidget) SetLogger(l func(string, ...any)) { w.logger = l } +func (w *DebugWidget) Log(txt string, args ...any) { + if w.logger != nil { + w.logger(txt, args...) + } +} + +func (w *DebugWidget) Id() string { return w.id } +func (w *DebugWidget) HandleResize(ev *tcell.EventResize) { + // Trim space for border and pass the resize to the widget + sw, sh := ev.Size() + if w.setW > 0 { + w.w = wh.Min(sw, w.WantW()) + } else { + w.w = sw + } + if w.setH > 0 { + w.h = wh.Min(sh, w.WantH()) + } else { + w.h = sh + } + + nX, nY := 1, 1 + if w.w > 9 { + nX = 2 + } + if w.h > 9 { + nY = 2 + } + w.mTL = Coord{X: nX, Y: nY} + w.mBR = Coord{X: 1, Y: 1} + + w.widget.SetPos(w.mTL) + w.widget.HandleResize(tcell.NewEventResize(w.w-w.mTL.X-w.mBR.X, w.h-w.mTL.Y-w.mBR.Y)) +} + +func (w *DebugWidget) HandleKey(ev *tcell.EventKey) bool { return w.widget.HandleKey(ev) } +func (w *DebugWidget) HandleTime(ev *tcell.EventTime) { w.widget.HandleTime(ev) } + +func (w *DebugWidget) Draw(screen tcell.Screen) { + if !w.visible { + return + } + st := w.style + if !w.active { + st = st.Dim(true) + } + + wh.Border(w.x+w.mTL.X-1, w.y+w.mTL.Y-1, w.x+w.w+w.mBR.X+1, w.y+w.h+w.mBR.Y+1, wh.BRD_CSIMPLE, st, screen) + + if w.drawRulers { + // X Ruler + var lastX rune + for i := 0; i < w.w; i++ { + str := fmt.Sprintf("%d", i) + var wrk rune + if len(str) == 3 { + wrk = rune(str[1]) + } else if len(str) == 2 { + wrk = rune(str[0]) + } + if wrk != lastX { + lastX = wrk + screen.SetContent(w.x+i+w.mTL.X, w.y, wrk, nil, st) + } + screen.SetContent(w.x+i+w.mTL.X, w.y+w.mTL.Y-1, rune(str[len(str)-1]), nil, st) + } + + // Y Ruler + var lastY rune + for i := 0; i < w.h; i++ { + str := fmt.Sprintf("%d", i) + if len(str) > 1 { + wrk := rune(str[0]) + if wrk != lastY { + lastY = wrk + screen.SetContent(w.x, w.y+i+w.mTL.Y, wrk, nil, st) + } + } + screen.SetContent(w.x+w.mTL.X-1, w.y+i+w.mTL.Y, rune(str[len(str)-1]), nil, st) + } + } + + w.GetPos().DrawOffset(w.widget, screen) +} + +func (w *DebugWidget) Active() bool { return w.active } +func (w *DebugWidget) SetActive(a bool) { + w.active = a + w.widget.SetActive(a) +} +func (w *DebugWidget) Visible() bool { return w.visible } +func (w *DebugWidget) SetVisible(a bool) { w.visible = a } +func (w *DebugWidget) Focusable() bool { return true } +func (w *DebugWidget) SetTabbable(b bool) { w.tabbable = b } +func (w *DebugWidget) Tabbable() bool { return w.tabbable } +func (w *DebugWidget) SetX(x int) { w.x = x } +func (w *DebugWidget) SetY(y int) { w.y = y } +func (w *DebugWidget) GetX() int { return w.x } +func (w *DebugWidget) GetY() int { return w.y } +func (w *DebugWidget) GetPos() Coord { return Coord{X: w.x, Y: w.y} } +func (w *DebugWidget) SetPos(c Coord) { w.x, w.y = c.X, c.Y } +func (w *DebugWidget) GetW() int { return w.w } +func (w *DebugWidget) GetH() int { return w.h } +func (w *DebugWidget) SetW(wd int) { + w.setW = wd + w.w = wd +} + +func (w *DebugWidget) SetH(h int) { + w.setH = h + w.h = h +} + +func (w *DebugWidget) SetSize(c Coord) { + w.SetW(c.X) + w.SetH(c.Y) +} + +func (w *DebugWidget) WantW() int { + if w.setW > 0 { + return w.setW + } + return w.w +} + +func (w *DebugWidget) WantH() int { + if w.setH > 0 { + return w.setH + } + return w.h +} +func (w *DebugWidget) MinW() int { return 2 + w.widget.MinW() } +func (w *DebugWidget) MinH() int { return 2 + w.widget.MinH() } + +func (w *DebugWidget) SetWidget(wd Widget) { w.widget = wd } +func (w *DebugWidget) EnableRulers(b bool) { w.drawRulers = b } diff --git a/field.go b/wdgt_field.go similarity index 96% rename from field.go rename to wdgt_field.go index 55517fe..0d47f03 100644 --- a/field.go +++ b/wdgt_field.go @@ -77,8 +77,8 @@ func (w *Field) Init(id string, style tcell.Style) { func (w *Field) Id() string { return w.id } func (w *Field) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) } func (w *Field) HandleKey(ev *tcell.EventKey) bool { @@ -137,12 +137,6 @@ func (w *Field) Draw(screen tcell.Screen) { wh.DrawText(x, w.y, post, useStyle, screen) } -func (w *Field) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *Field) Active() bool { return w.active } func (w *Field) SetActive(a bool) { w.active = a } func (w *Field) Visible() bool { return w.visible } diff --git a/filepicker.go b/wdgt_filepicker.go similarity index 95% rename from filepicker.go rename to wdgt_filepicker.go index fae2fb6..5b759a4 100644 --- a/filepicker.go +++ b/wdgt_filepicker.go @@ -100,17 +100,10 @@ func (w *FilePicker) Draw(screen tcell.Screen) { wh.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, wh.BRD_SIMPLE, ds, screen) // TODO: Draw the file picker wh.DrawText(w.x+1, w.y+1, "TODO: Draw Filepicker", ds, screen) - c := w.GetPos() - w.btnSelect.DrawOffset(c, screen) - w.btnCancel.DrawOffset(c, screen) + w.GetPos().DrawOffset(w.btnSelect, screen) + w.GetPos().DrawOffset(w.btnCancel, screen) } -func (w *FilePicker) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *FilePicker) Active() bool { return w.active } func (w *FilePicker) SetActive(a bool) { w.active = a } func (w *FilePicker) Visible() bool { return w.visible } diff --git a/linear_layout.go b/wdgt_linear_layout.go similarity index 74% rename from linear_layout.go rename to wdgt_linear_layout.go index 8fa1c79..6986568 100644 --- a/linear_layout.go +++ b/wdgt_linear_layout.go @@ -27,6 +27,7 @@ import ( ) // LinearLayout lays out all widgets added one after the other +// It will fill as much space as you give it type LinearLayout struct { id string style tcell.Style @@ -75,45 +76,23 @@ func (w *LinearLayout) Init(id string, s tcell.Style) { func (w *LinearLayout) Id() string { return w.id } func (w *LinearLayout) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Max(w.w, w.WantW()) + // w.h = wh.Max(w.h, w.WantH()) w.updateWidgetLayouts() } func (w *LinearLayout) HandleKey(ev *tcell.EventKey) bool { - if !w.disableTab && ev.Key() == tcell.KeyTab { - fndP := -1 - for i := w.cursor; i < len(w.widgets); i++ { - if fndP == -1 { - if w.widgets[i].Active() { - fndP = i - w.widgets[i].SetActive(false) - continue - } - } else { - if w.widgets[i].Focusable() && w.widgets[i].Tabbable() { - w.widgets[i].SetActive(true) - return true - } - } - } - // If we're here, we hit the last widget, loop - if fndP == -1 { // But didn't even find the active one - return false - } - for i := 0; i < fndP; i++ { - if w.widgets[i].Focusable() && w.widgets[i].Tabbable() { - w.widgets[i].SetActive(true) - return true - } - } - return false - } - for _, wi := range w.widgets { - if wi.HandleKey(ev) { + // First, see if the active widget handles the key + if len(w.widgets) > w.cursor { + if w.widgets[w.cursor].HandleKey(ev) { return true } } + + if ev.Key() == tcell.KeyTab && !w.disableTab { + return w.handleTab() + } + return false } @@ -127,21 +106,17 @@ func (w *LinearLayout) Draw(screen tcell.Screen) { if !w.visible { return } - p := w.GetPos() + pos := w.GetPos() for _, wd := range w.widgets { - wd.DrawOffset(p, screen) + pos.DrawOffset(wd, screen) } } -func (w *LinearLayout) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) +func (w *LinearLayout) Active() bool { return w.active } +func (w *LinearLayout) SetActive(a bool) { + w.active = a + w.updateActive() } - -func (w *LinearLayout) Active() bool { return w.active } -func (w *LinearLayout) SetActive(a bool) { w.active = a } func (w *LinearLayout) Visible() bool { return w.visible } func (w *LinearLayout) SetVisible(a bool) { w.visible = a } func (w *LinearLayout) Focusable() bool { return true } @@ -216,6 +191,15 @@ func (w *LinearLayout) MinH() int { return minH } +// Find the widget at 'cursor' and set it active. +// All others to inactive +// If this layout is not active, everything is inactive +func (w *LinearLayout) updateActive() { + for i, wd := range w.widgets { + wd.SetActive(i == w.cursor && w.active) + } +} + func (w *LinearLayout) SetOrientation(o LinearLayoutOrient) { w.orientation = o } func (w *LinearLayout) IndexOf(n Widget) int { for i := range w.widgets { @@ -323,39 +307,38 @@ func (w *LinearLayout) updateWidgetLayouts() { switch w.orientation { case LinLayV: for _, wd := range w.widgets { - w.updateVerticalWidgetSize(wd) - w.updateVerticalWidgetPos(wd) + w.updateLLVWidgetSize(wd) + w.updateLLVWidgetPos(wd) } case LinLayH: for _, wd := range w.widgets { - w.updateHorizontalWidgetSize(wd) - w.updateHorizontalWidgetPos(wd) + w.updateLLHWidgetSize(wd) + w.updateLLHWidgetPos(wd) } } } // The Layout should have a static Size set at this point that we can use -// For now we're centering all views in the Layout (on the cross-axis) +// For now we're centering all views in the Layout (on both axes) // // We need to determine the allowed size of this widget so we can determine // it's position -func (w *LinearLayout) updateVerticalWidgetSize(wd Widget) { +func (w *LinearLayout) updateLLVWidgetSize(wd Widget) { wd.HandleResize((&Coord{X: w.w, Y: w.getWeightedH(wd)}).ResizeEvent()) } -func (w *LinearLayout) updateHorizontalWidgetSize(wd Widget) { +func (w *LinearLayout) updateLLHWidgetSize(wd Widget) { wd.HandleResize((&Coord{X: w.getWeightedW(wd), Y: w.h}).ResizeEvent()) } // The Layout should have a static Size set at this point that we can use -// For now we're centering all views in the Layout (on the cross-axis) +// For now we're centering all views in the Layout (on both axes) // TODO: Use LayoutFlags to determine alignment in each 'cell' // // The position and size of each widget before this should be correct // This widget should also know its size by now. We just need to // position it relative to the layout. -func (w *LinearLayout) updateVerticalWidgetPos(wd Widget) { - w.Log("Calculating Widget Pos: %s", wd.Id()) +func (w *LinearLayout) updateLLVWidgetPos(wd Widget) { c := Coord{} for i := range w.widgets { if w.widgets[i] == wd { @@ -363,17 +346,54 @@ func (w *LinearLayout) updateVerticalWidgetPos(wd Widget) { } c.Y += w.getWeightedH(w.widgets[i]) } - w.Log("Set %s Y = %d", wd.Id(), c.Y) - c.X = (w.w / 2) - (wd.GetW() / 2) + + // Do we have a layout flag for this widget? + var ok bool + var flgs LayoutFlag + if flgs, ok = w.layoutFlags[wd]; !ok { + flgs = LayoutFlag(LFAlignHCenter | LFAlignVCenter) + } + + // c.Y is the top of this 'cell' + if wd.GetH() < w.getWeightedH(wd) { + // But we've got some extra space + switch flgs.AlignV() { + case LFAlignVBottom: + c.Y += w.getWeightedH(wd) - wd.GetH() + case LFAlignVCenter: + c.Y += (w.getWeightedH(wd) / 2) - (wd.GetH() / 2) + } + } + c.X = int((float64(w.w) / 2) - (float64(wd.GetW()) / 2)) wd.SetPos(c) } -func (w *LinearLayout) updateHorizontalWidgetPos(wd Widget) { +func (w *LinearLayout) updateLLHWidgetPos(wd Widget) { c := Coord{} for i := range w.widgets { - c.X += w.getWeightedH(w.widgets[i]) + if w.widgets[i] == wd { + break + } + c.X += w.getWeightedW(w.widgets[i]) } - c.Y = (w.h / 2) - (wd.GetH() / 2) + // Do we have a layout flag for this widget? + var ok bool + var flgs LayoutFlag + if flgs, ok = w.layoutFlags[wd]; !ok { + flgs = LayoutFlag(LFAlignHCenter | LFAlignVCenter) + } + + // c.X is the left-most of this 'cell' + if wd.GetW() < w.getWeightedW(wd) { + // But we've got some extra space. + switch flgs.AlignH() { + case LFAlignHRight: + c.X += w.getWeightedW(wd) - wd.GetW() + case LFAlignHCenter: + c.X += (w.getWeightedW(wd) / 2) - (wd.GetW() / 2) + } + } + c.Y = int((float64(w.h) / 2) - (float64(wd.GetH()) / 2)) wd.SetPos(c) } @@ -383,38 +403,49 @@ func (w *LinearLayout) updateWidgetPos(wd Widget) { c := Coord{} switch w.orientation { case LinLayV: - c.X, c.Y = w.w-(wd.GetW()/2), prevP+prevS+1 - prevP, prevS = wrk.GetY(), wrk.GetH() + + c.X, c.Y = 0, prevP+prevS + prevP, prevS = c.Y, wrk.GetH() case LinLayH: - c.X, c.Y = prevP+prevS+1, w.h-(wd.GetH()/2) - prevP, prevS = wrk.GetX(), wrk.GetW() + c.X, c.Y = prevP+prevS, 0 + prevP, prevS = c.X, wrk.GetW() } wd.SetPos(c) } } -func (w *LinearLayout) getRelPos(wd Widget) Coord { - _ = wd - return Coord{} -} - -func (w *LinearLayout) getAbsPos(wd Widget) Coord { - rel := w.getRelPos(wd) - return rel.Add(Coord{X: w.x, Y: w.y}) -} - func (w *LinearLayout) getWeightedH(wd Widget) int { if !w.Contains(wd) { return 0 } - return w.h * (w.layoutWeights[wd] * w.totalWeight) + wght := w.layoutWeights[wd] + if wght == 0 { + wght = 1 + } + ret := int(float64(w.h) * (float64(wght) / float64(w.totalWeight))) + return ret } func (w *LinearLayout) getWeightedW(wd Widget) int { if !w.Contains(wd) { return 0 } - return w.w * (w.layoutWeights[wd] * w.totalWeight) + wght := w.layoutWeights[wd] + if wght == 0 { + wght = 1 + } + return int(float64(w.w) * (float64(wght) / float64(w.totalWeight))) +} + +func (w *LinearLayout) handleTab() bool { + beg := w.cursor + // Find the next tabbable widget + w.cursor = (w.cursor + 1) % len(w.widgets) + for !w.widgets[w.cursor].Tabbable() && beg != w.cursor { + w.cursor = (w.cursor + 1) % len(w.widgets) + } + w.updateActive() + return w.cursor > 0 && w.cursor != beg } func (w *LinearLayout) SetLogger(l func(string, ...any)) { w.logger = l } diff --git a/list.go b/wdgt_list.go similarity index 95% rename from list.go rename to wdgt_list.go index 0c91b1b..38ca253 100644 --- a/list.go +++ b/wdgt_list.go @@ -47,6 +47,8 @@ type List struct { onSelect func(int, string) bool keyMap KeyMap vimMode bool + + logger func(string, ...any) } var _ Widget = (*List)(nil) @@ -60,6 +62,7 @@ func NewList(id string, style tcell.Style) *List { func (w *List) Init(id string, style tcell.Style) { w.id = id w.style = style + w.focusable = true w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{ tcell.KeyUp: w.MoveUp, tcell.KeyDown: w.MoveDown, @@ -88,9 +91,9 @@ func (w *List) Init(id string, style tcell.Style) { func (w *List) Id() string { return w.id } func (w *List) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // Fill as much space as we're given + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) } func (w *List) HandleKey(ev *tcell.EventKey) bool { @@ -135,12 +138,6 @@ func (w *List) Draw(screen tcell.Screen) { } } -func (w *List) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *List) Active() bool { return w.active } func (w *List) SetActive(a bool) { w.active = a } func (w *List) Visible() bool { return w.visible } @@ -244,3 +241,10 @@ func (w *List) GetIdx() int { return w.cursor } func (w *List) ClearBorder() { w.border = []rune{} } func (w *List) SetOnSelect(s func(int, string) bool) { w.onSelect = s } func (w *List) SetVimMode(b bool) { w.vimMode = b } + +func (w *List) SetLogger(l func(string, ...any)) { w.logger = l } +func (w *List) Log(txt string, args ...any) { + if w.logger != nil { + w.logger(txt, args...) + } +} diff --git a/menu.go b/wdgt_menu.go similarity index 97% rename from menu.go rename to wdgt_menu.go index 21f5860..9255ca1 100644 --- a/menu.go +++ b/wdgt_menu.go @@ -87,8 +87,8 @@ func (w *Menu) Id() string { return w.id } func (w *Menu) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) // TODO: Trickle-down HandleResize } @@ -116,13 +116,6 @@ func (w *Menu) Draw(screen tcell.Screen) { } } -func (w *Menu) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} - func (w *Menu) drawHMenu(screen tcell.Screen) { st := w.style if w.active { diff --git a/menu_item.go b/wdgt_menu_item.go similarity index 96% rename from menu_item.go rename to wdgt_menu_item.go index cfe8a50..a5256fd 100644 --- a/menu_item.go +++ b/wdgt_menu_item.go @@ -81,8 +81,8 @@ func (w *MenuItem) Id() string { return w.id } func (w *MenuItem) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) // TODO: Trickle-down HandleResize } @@ -125,12 +125,6 @@ func (w *MenuItem) Draw(screen tcell.Screen) { } } -func (w *MenuItem) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *MenuItem) Active() bool { return w.active } func (w *MenuItem) SetActive(a bool) { w.active = a diff --git a/prompt.go b/wdgt_prompt.go similarity index 94% rename from prompt.go rename to wdgt_prompt.go index 9e937d6..57e2543 100644 --- a/prompt.go +++ b/wdgt_prompt.go @@ -70,6 +70,7 @@ func (w *Prompt) Id() string { return w.id } func (w *Prompt) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() + // Prompt is shrink-wrapped by default w.w = wh.Min(w.w, w.WantW()) w.h = wh.Min(w.h, w.WantH()) @@ -95,18 +96,11 @@ func (w *Prompt) Draw(screen tcell.Screen) { dS = dS.Dim(true) } wh.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, wh.BRD_SIMPLE, w.style, screen) - p := w.GetPos() - w.message.DrawOffset(p, screen) - w.btnOk.DrawOffset(p, screen) - w.btnCancel.DrawOffset(p, screen) + w.GetPos().DrawOffset(w.message, screen) + w.GetPos().DrawOffset(w.btnOk, screen) + w.GetPos().DrawOffset(w.btnCancel, screen) } -func (w *Prompt) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *Prompt) Active() bool { return w.active } func (w *Prompt) SetActive(a bool) { w.active = a } func (w *Prompt) Visible() bool { return w.visible } diff --git a/relative_layout.go b/wdgt_relative_layout.go similarity index 95% rename from relative_layout.go rename to wdgt_relative_layout.go index 99d4b37..a018b1c 100644 --- a/relative_layout.go +++ b/wdgt_relative_layout.go @@ -22,7 +22,6 @@ THE SOFTWARE. package widgets import ( - wh "git.bullercodeworks.com/brian/tcell-widgets/helpers" "github.com/gdamore/tcell" ) @@ -81,8 +80,8 @@ func (w *RelativeLayout) Id() string { return w.id } func (w *RelativeLayout) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) // TODO: Trickle-down HandleResize } @@ -106,12 +105,6 @@ func (w *RelativeLayout) Draw(screen tcell.Screen) { */ } -func (w *RelativeLayout) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *RelativeLayout) Active() bool { return w.active } func (w *RelativeLayout) SetActive(a bool) { w.active = a } func (w *RelativeLayout) Visible() bool { return w.visible } diff --git a/searcher.go b/wdgt_searcher.go similarity index 97% rename from searcher.go rename to wdgt_searcher.go index 8869683..033319d 100644 --- a/searcher.go +++ b/wdgt_searcher.go @@ -85,8 +85,8 @@ func (w *Searcher) Id() string { return w.id } func (w *Searcher) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) // TODO: Verify this is fine: w.search.HandleResize(ev) } @@ -184,7 +184,7 @@ func (w *Searcher) Draw(screen tcell.Screen) { } else { wh.BorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, wh.BRD_CSIMPLE, dStyle, screen) } - w.search.DrawOffset(w.GetPos(), screen) + w.GetPos().DrawOffset(w.search, screen) x, y := w.x+1, w.y+2 var stIdx int if w.cursor > w.h/2 { @@ -208,12 +208,6 @@ func (w *Searcher) Draw(screen tcell.Screen) { } } -func (w *Searcher) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *Searcher) Active() bool { return w.active } func (w *Searcher) SetActive(a bool) { w.active = a diff --git a/table.go b/wdgt_table.go similarity index 97% rename from table.go rename to wdgt_table.go index 381b155..112385f 100644 --- a/table.go +++ b/wdgt_table.go @@ -88,8 +88,8 @@ func (w *Table) Id() string { return w.id } func (w *Table) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) } func (w *Table) HandleKey(ev *tcell.EventKey) bool { @@ -147,12 +147,6 @@ func (w *Table) Draw(screen tcell.Screen) { } } -func (w *Table) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *Table) Active() bool { return w.active } func (w *Table) SetActive(a bool) { w.active = a } func (w *Table) Visible() bool { return w.visible } diff --git a/text.go b/wdgt_text.go similarity index 95% rename from text.go rename to wdgt_text.go index 78f2aaf..e76ba61 100644 --- a/text.go +++ b/wdgt_text.go @@ -58,8 +58,8 @@ func (w *Text) Init(id string, style tcell.Style) { func (w *Text) Id() string { return w.id } func (w *Text) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) } func (w *Text) HandleKey(ev *tcell.EventKey) bool { return false } func (w *Text) HandleTime(ev *tcell.EventTime) {} @@ -74,12 +74,6 @@ func (w *Text) Draw(screen tcell.Screen) { } } -func (w *Text) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} func (w *Text) Active() bool { return false } func (w *Text) SetActive(a bool) {} func (w *Text) Visible() bool { return w.visible } diff --git a/timefield.go b/wdgt_timefield.go similarity index 97% rename from timefield.go rename to wdgt_timefield.go index e8fd05a..ed6991c 100644 --- a/timefield.go +++ b/wdgt_timefield.go @@ -82,8 +82,8 @@ func (w *TimeField) Id() string { return w.id } func (w *TimeField) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - w.w = wh.Min(w.w, w.WantW()) - w.h = wh.Min(w.h, w.WantH()) + // w.w = wh.Min(w.w, w.WantW()) + // w.h = wh.Min(w.h, w.WantH()) } func (w *TimeField) HandleKey(ev *tcell.EventKey) bool { @@ -158,13 +158,6 @@ func (w *TimeField) Draw(screen tcell.Screen) { } } -func (w *TimeField) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} - func (w *TimeField) Active() bool { return w.active } func (w *TimeField) SetActive(a bool) { w.active = a } func (w *TimeField) Visible() bool { return w.visible } diff --git a/top_menu_layout.go b/wdgt_top_menu_layout.go similarity index 89% rename from top_menu_layout.go rename to wdgt_top_menu_layout.go index 6237631..16209eb 100644 --- a/top_menu_layout.go +++ b/wdgt_top_menu_layout.go @@ -22,6 +22,8 @@ THE SOFTWARE. package widgets import ( + "fmt" + wh "git.bullercodeworks.com/brian/tcell-widgets/helpers" "github.com/gdamore/tcell" ) @@ -39,6 +41,8 @@ type TopMenuLayout struct { active bool visible bool + + logger func(string, ...any) } var _ Widget = (*TopMenuLayout)(nil) @@ -54,7 +58,7 @@ func (w *TopMenuLayout) Init(id string, s tcell.Style) { w.style = s w.visible = true - w.menu = NewMenu("mainmenu", tcell.StyleDefault) + w.menu = NewMenu(fmt.Sprintf("%s.mainmenu", id), tcell.StyleDefault) w.menu.SetActive(false) w.menu.SetType(MenuTypeH) w.menu.SetTabbable(false) @@ -63,15 +67,14 @@ func (w *TopMenuLayout) Init(id string, s tcell.Style) { func (w *TopMenuLayout) Id() string { return w.id } func (w *TopMenuLayout) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() - availW, availH := w.w, w.h if w.menu != nil { w.menu.SetPos(Coord{X: 0, Y: 0}) w.menu.SetSize(Coord{X: w.w, Y: 1}) - availH = w.h - 1 } + if w.widget != nil { w.widget.SetPos(Coord{X: 0, Y: 1}) - w.widget.HandleResize(tcell.NewEventResize(availW-1, availH-1)) + w.widget.HandleResize(tcell.NewEventResize(w.w, w.h-1)) } } @@ -103,22 +106,14 @@ func (w *TopMenuLayout) Draw(screen tcell.Screen) { if !w.visible { return } - p := w.GetPos() if w.menu != nil { - w.menu.DrawOffset(p, screen) + w.GetPos().DrawOffset(w.menu, screen) } if w.widget != nil { - w.widget.DrawOffset(p, screen) + w.GetPos().DrawOffset(w.widget, screen) } } -func (w *TopMenuLayout) DrawOffset(c Coord, screen tcell.Screen) { - p := w.GetPos() - w.SetPos(p.Add(c)) - w.Draw(screen) - w.SetPos(p) -} - func (w *TopMenuLayout) Active() bool { return w.active } func (w *TopMenuLayout) SetActive(a bool) { w.active = a } func (w *TopMenuLayout) Visible() bool { return w.visible } @@ -146,4 +141,12 @@ func (w *TopMenuLayout) MinH() int { return 0 } func (w *TopMenuLayout) Menu() *Menu { return w.menu } func (w *TopMenuLayout) AddMenuItems(iL ...*MenuItem) { w.menu.AddItems(iL...) } func (w *TopMenuLayout) RemoveMenuItems(iL ...*MenuItem) { w.menu.RemoveItems(iL...) } -func (w *TopMenuLayout) SetWidget(wd Widget) { w.widget = wd } + +func (w *TopMenuLayout) SetWidget(wd Widget) { w.widget = wd } + +func (w *TopMenuLayout) SetLogger(l func(string, ...any)) { w.logger = l } +func (w *TopMenuLayout) Log(txt string, args ...any) { + if w.logger != nil { + w.logger(txt, args...) + } +} diff --git a/widget.go b/widget.go index 00a94ce..7a9bde9 100644 --- a/widget.go +++ b/widget.go @@ -33,7 +33,6 @@ type Widget interface { HandleKey(*tcell.EventKey) bool HandleTime(*tcell.EventTime) Draw(tcell.Screen) - DrawOffset(Coord, tcell.Screen) Active() bool SetActive(bool) Visible() bool @@ -73,21 +72,6 @@ func WidgetRight(w Widget) int { return w.GetX() + w.GetW() } -type Coord struct { - X, Y int -} - -func (p *Coord) Add(o Coord) Coord { - return Coord{ - X: p.X + o.X, - Y: p.Y + o.Y, - } -} - -func (p *Coord) ResizeEvent() *tcell.EventResize { - return tcell.NewEventResize(p.X, p.Y) -} - // To validate that a struct satisfies this interface, you can do: // var _ Widget - (*)(nil) // where is the actual struct that you're validating.