From acfe8be93d8060defc5a00d611b696d63bbbc81d Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Thu, 14 Aug 2025 14:21:19 -0500 Subject: [PATCH] Working some things out --- example/ui.go | 11 ++++++ example/ui_screen.go | 56 +++++++++++++++------------ wdgt_blank.go | 67 ++++++++++++++++++++++++++++++++ wdgt_bordered.go | 26 +++---------- wdgt_button.go | 2 + wdgt_debug.go | 37 ++++++++++-------- wdgt_linear_layout.go | 86 ++++++++++++++++++++++++----------------- wdgt_searcher.go | 10 ++--- wdgt_top_menu_layout.go | 2 +- 9 files changed, 193 insertions(+), 104 deletions(-) create mode 100644 wdgt_blank.go diff --git a/example/ui.go b/example/ui.go index a4d03a3..40747d3 100644 --- a/example/ui.go +++ b/example/ui.go @@ -23,6 +23,7 @@ package main import ( "fmt" + "math/rand" "os" "github.com/gdamore/tcell" @@ -116,3 +117,13 @@ func (ui *Ui) SetScreen(scr *UiScreen) { func (ui *Ui) stop() { ui.running = false } func (ui *Ui) cleanup() { ui.tScreen.Fini() } + +const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +func RandomString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} diff --git a/example/ui_screen.go b/example/ui_screen.go index ae54592..183590f 100644 --- a/example/ui_screen.go +++ b/example/ui_screen.go @@ -22,8 +22,6 @@ THE SOFTWARE. package main import ( - "math/rand" - w "git.bullercodeworks.com/brian/tcell-widgets" "github.com/gdamore/tcell" ) @@ -33,6 +31,7 @@ type UiScreen struct { w, h int widget w.Widget + ll *w.LinearLayout log *w.Cli widgets []w.Widget @@ -44,27 +43,31 @@ func (s *UiScreen) Init(ui *Ui) { s.ui = ui s.log = w.NewCli("log", ui.style) - ll := w.NewLinearLayout("test", ui.style) - ll.SetTabbable(true) - ll.SetLogger(s.log.Log) + s.ll = w.NewLinearLayout("top", ui.style) + s.ll.SetLogger(s.log.Log) + + hl := w.NewLinearLayout("dat", ui.style) + hl.SetOrientation(w.LinLayH) + hl.SetLogger(s.log.Log) searcher := w.NewSearcher("test.searcher", ui.style) searcher.SetLogger(s.log.Log) searcher.SetTitle("Test Searcher") - const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - rStr := func(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = letters[rand.Intn(len(letters))] - } - return string(b) - } var dat []string for i := 0; i < 1000; i++ { - dat = append(dat, rStr(15)) + dat = append(dat, RandomString(15)) } searcher.SetData(dat) - ll.Add(searcher) + hl.Add(searcher) + + db := w.NewButton("test-bd-1", ui.style) + db.SetLabel("Button 1") + db.SetOnPressed( + hl.Add(db) + db = w.NewButton("test-bd-2", ui.style) + db.SetLabel("Button 2") + hl.Add(db) + s.ll.Add(hl) btnL := w.NewLinearLayout("test.btnll", ui.style) btnL.SetTabbable(true) @@ -80,8 +83,8 @@ func (s *UiScreen) Init(ui *Ui) { btnOk.SetLabel("Ok") btnL.Add(btnOk) btnL.AddFlag(btnOk, w.LFAlignVBottom) - ll.Add(btnL) - ll.AddFlag(btnL, w.LFAlignVBottom) + s.ll.Add(btnL) + s.ll.AddFlag(btnL, w.LFAlignVBottom) ml := w.NewTopMenuLayout("menu", ui.style) m := ml.Menu() @@ -95,7 +98,7 @@ func (s *UiScreen) Init(ui *Ui) { ), ) ml.SetLogger(s.log.Log) - ml.SetWidget(ll) + ml.SetWidget(s.ll) ml.SetActive(true) s.widget = ml /* @@ -105,7 +108,7 @@ func (s *UiScreen) Init(ui *Ui) { dw.SetActive(true) s.widget = dw */ - ll.Add(s.log) + s.ll.Add(s.log) // s.widgets = append(s.widgets, ml) // s.widgets = append(s.widgets, s.log) @@ -114,6 +117,7 @@ func (s *UiScreen) Init(ui *Ui) { 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()) @@ -124,13 +128,17 @@ func (s *UiScreen) HandleKey(ev *tcell.EventKey) bool { // Ctrl+J is the keypad 'Enter' ev = tcell.NewEventKey(tcell.KeyEnter, 0, 0) } + if ev.Key() == tcell.KeyF12 { + if s.ll.Contains(s.log) { + s.ll.Delete(s.log) + } else { + s.ll.Add(s.log) + } + } return s.widget.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) Draw() { s.widget.Draw(s.ui.tScreen) } +func (s *UiScreen) Log(txt string, args ...any) {} func (s *UiScreen) Exit() {} diff --git a/wdgt_blank.go b/wdgt_blank.go new file mode 100644 index 0000000..c95472b --- /dev/null +++ b/wdgt_blank.go @@ -0,0 +1,67 @@ +/* +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" + +// BlankWidget is just blank. It's a good placeholder, if needed. +type BlankWidget struct { + id string + x, y int +} + +var _ Widget = (*BlankWidget)(nil) + +func NewBlankWidget(id string) *BlankWidget { + ret := &BlankWidget{id: id} + return ret +} + +func (w *BlankWidget) Init(id string, st tcell.Style) {} +func (w *BlankWidget) Id() string { return w.id } +func (w *BlankWidget) HandleResize(ev *tcell.EventResize) {} +func (w *BlankWidget) HandleKey(ev *tcell.EventKey) bool { return false } +func (w *BlankWidget) HandleTime(ev *tcell.EventTime) {} +func (w *BlankWidget) Draw(screen tcell.Screen) {} + +func (w *BlankWidget) Active() bool { return false } +func (w *BlankWidget) SetActive(a bool) {} +func (w *BlankWidget) Visible() bool { return true } +func (w *BlankWidget) SetVisible(v bool) {} +func (w *BlankWidget) Focusable() bool { return false } +func (w *BlankWidget) Tabbable() bool { return false } +func (w *BlankWidget) SetTabbable(t bool) {} +func (w *BlankWidget) SetX(x int) {} +func (w *BlankWidget) SetY(y int) {} +func (w *BlankWidget) GetX() int { return w.x } +func (w *BlankWidget) GetY() int { return w.y } +func (w *BlankWidget) GetPos() Coord { return Coord{X: w.x, Y: w.y} } +func (w *BlankWidget) SetPos(pos Coord) {} +func (w *BlankWidget) SetSize(size Coord) {} +func (w *BlankWidget) SetW(wd int) {} +func (w *BlankWidget) SetH(h int) {} +func (w *BlankWidget) GetW() int { return 0 } +func (w *BlankWidget) GetH() int { return 0 } +func (w *BlankWidget) WantW() int { return 0 } +func (w *BlankWidget) WantH() int { return 0 } +func (w *BlankWidget) MinW() int { return 0 } +func (w *BlankWidget) MinH() int { return 0 } diff --git a/wdgt_bordered.go b/wdgt_bordered.go index ae4f173..886fcd0 100644 --- a/wdgt_bordered.go +++ b/wdgt_bordered.go @@ -46,17 +46,8 @@ type BorderedWidget struct { var _ Widget = (*BorderedWidget)(nil) -func (w *BorderedWidget) SetLogger(l func(string)) { w.logger = l } -func (w *BorderedWidget) Log(txt string) { - if w.logger != nil { - w.logger(txt) - } -} - -func NewBorderedWidget(id string, wd Widget, s tcell.Style) *BorderedWidget { - ret := &BorderedWidget{ - widget: wd, - } +func NewBorderedWidget(id string, s tcell.Style, wd Widget) *BorderedWidget { + ret := &BorderedWidget{widget: wd} ret.Init(id, s) return ret } @@ -73,19 +64,13 @@ 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.widget.SetPos(Coord{X: 1, Y: 1}) w.widget.HandleResize(tcell.NewEventResize(w.w-2, w.h-2)) } -func (w *BorderedWidget) HandleKey(ev *tcell.EventKey) bool { - return w.HandleKey(ev) -} +func (w *BorderedWidget) HandleKey(ev *tcell.EventKey) bool { return w.HandleKey(ev) } -func (w *BorderedWidget) HandleTime(ev *tcell.EventTime) { - w.HandleTime(ev) -} +func (w *BorderedWidget) HandleTime(ev *tcell.EventTime) { w.HandleTime(ev) } func (w *BorderedWidget) Draw(screen tcell.Screen) { if !w.visible { @@ -96,7 +81,6 @@ func (w *BorderedWidget) Draw(screen tcell.Screen) { } else { 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.GetPos().DrawOffset(w, screen) } diff --git a/wdgt_button.go b/wdgt_button.go index a84ed29..dc5a0f4 100644 --- a/wdgt_button.go +++ b/wdgt_button.go @@ -82,6 +82,8 @@ func (w *Button) Draw(screen tcell.Screen) { dStyle := w.style if w.active { dStyle = w.style.Bold(true) + } else { + dStyle = w.style.Dim(true) } switch w.h { case 1: diff --git a/wdgt_debug.go b/wdgt_debug.go index 30a0202..70ea874 100644 --- a/wdgt_debug.go +++ b/wdgt_debug.go @@ -28,7 +28,6 @@ import ( "github.com/gdamore/tcell" ) -// TODO: Make sure this works right... I don't think it does. type DebugWidget struct { id string style tcell.Style @@ -50,7 +49,9 @@ type DebugWidget struct { var _ Widget = (*DebugWidget)(nil) func NewDebugWidget(id string, s tcell.Style) *DebugWidget { - ret := &DebugWidget{} + ret := &DebugWidget{ + widget: NewBlankWidget(fmt.Sprintf("%s-placeholder", id)), + } ret.Init(id, s) return ret } @@ -86,16 +87,19 @@ func (w *DebugWidget) HandleResize(ev *tcell.EventResize) { w.h = sh } - nX, nY := 1, 1 - if w.w > 9 { - nX = 2 + if w.drawRulers { + 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} + } else { + w.mTL, w.mBR = Coord{X: 0, Y: 0}, Coord{X: 0, Y: 0} } - 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)) } @@ -107,12 +111,13 @@ func (w *DebugWidget) Draw(screen tcell.Screen) { if !w.visible { return } - st := w.style - if !w.active { - st = st.Dim(true) - } + st := w.style.Dim(!w.active) - 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 { + 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) + } else { + wh.Border(w.x, w.y, w.x+w.w, w.y+w.h, wh.BRD_CSIMPLE, st, screen) + } if w.drawRulers { // X Ruler diff --git a/wdgt_linear_layout.go b/wdgt_linear_layout.go index 8b96d71..2cd9c88 100644 --- a/wdgt_linear_layout.go +++ b/wdgt_linear_layout.go @@ -77,16 +77,12 @@ 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.Max(w.w, w.WantW()) - // w.h = wh.Max(w.h, w.WantH()) w.updateWidgetLayouts() } func (w *LinearLayout) HandleKey(ev *tcell.EventKey) bool { // First, see if the active widget handles the key - w.Log("LL: Handling Event: %s", ev.Name()) if len(w.widgets) > w.cursor { - w.Log("LL: Pushing to Widget: %s", w.widgets[w.cursor].Id()) if w.widgets[w.cursor].HandleKey(ev) { return true } @@ -135,6 +131,7 @@ func (w *LinearLayout) GetW() int { return w.w } func (w *LinearLayout) GetH() int { return w.h } func (w *LinearLayout) SetW(wd int) { w.w = wd } func (w *LinearLayout) SetH(h int) { w.h = h } +func (w *LinearLayout) getSize() Coord { return Coord{X: w.w, Y: w.h} } func (w *LinearLayout) SetSize(c Coord) { w.w, w.h = c.X, c.Y } func (w *LinearLayout) WantW() int { var wantW int @@ -144,7 +141,7 @@ func (w *LinearLayout) WantW() int { // Find the highest want of all widgets wantW = wh.Max(wd.WantW(), wantW) case LinLayH: - // Find the sum of all widget widgets wants + // Find the sum of all widgets wants wantW = wantW + wd.WantW() } } @@ -156,7 +153,7 @@ func (w *LinearLayout) WantH() int { for _, wd := range w.widgets { switch w.orientation { case LinLayV: - // Find the sum of all widget widgets wants + // Find the sum of all widgets wants wantH = wantH + wd.WantH() case LinLayH: // Find the highest want of all widgets @@ -198,13 +195,7 @@ func (w *LinearLayout) MinH() int { // All others to inactive // If this layout is not active, everything is inactive func (w *LinearLayout) updateActive() { - if !w.active { - w.Log("LL: Not Active") - } for i, wd := range w.widgets { - if i == w.cursor { - w.Log("LL: Setting Active: %s", w.widgets[i].Id()) - } wd.SetActive(i == w.cursor && w.active) } } @@ -264,6 +255,8 @@ func (w *LinearLayout) Delete(n Widget) { for i := 0; i < len(w.widgets); i++ { if w.widgets[i] == n { w.DeleteIndex(i) + delete(w.layoutFlags, n) + delete(w.layoutWeights, n) return } } @@ -310,6 +303,7 @@ func (w *LinearLayout) updateTotalWeight() { for _, v := range w.layoutWeights { w.totalWeight += v } + w.HandleResize(w.getSize().ResizeEvent()) } func (w *LinearLayout) updateWidgetLayouts() { @@ -333,11 +327,25 @@ func (w *LinearLayout) updateWidgetLayouts() { // We need to determine the allowed size of this widget so we can determine // it's position func (w *LinearLayout) updateLLVWidgetSize(wd Widget) { - wd.HandleResize((&Coord{X: w.w, Y: w.getWeightedH(wd)}).ResizeEvent()) + rW := w.w + if wd == w.widgets[len(w.widgets)-1] { + wrk := float64(w.w) / float64(w.totalWeight) + if wrk == float64((w.w / w.totalWeight)) { + rW -= 1 + } + } + wd.HandleResize((&Coord{X: rW, Y: w.getWeightedH(wd)}).ResizeEvent()) } func (w *LinearLayout) updateLLHWidgetSize(wd Widget) { - wd.HandleResize((&Coord{X: w.getWeightedW(wd), Y: w.h}).ResizeEvent()) + rH := w.h + if wd == w.widgets[len(w.widgets)-1] { + wrk := float64(w.h) / float64(w.totalWeight) + if wrk == float64((w.h / w.totalWeight)) { + rH -= 1 + } + } + wd.HandleResize((&Coord{X: w.getWeightedW(wd), Y: rH}).ResizeEvent()) } // The Layout should have a static Size set at this point that we can use @@ -373,7 +381,11 @@ func (w *LinearLayout) updateLLVWidgetPos(wd Widget) { c.Y += (w.getWeightedH(wd) / 2) - (wd.GetH() / 2) } } - c.X = int((float64(w.w) / 2) - (float64(wd.GetW()) / 2)) + if wd.GetW() < w.w { + c.X = int((float64(w.w) / 2) - (float64(wd.GetW()) / 2)) + } else { + c.X = 0 + } wd.SetPos(c) } @@ -381,10 +393,14 @@ func (w *LinearLayout) updateLLHWidgetPos(wd Widget) { c := Coord{} for i := range w.widgets { if w.widgets[i] == wd { + if i > 0 { + c.X += 1 + } break } c.X += w.getWeightedW(w.widgets[i]) } + // Do we have a layout flag for this widget? var ok bool var flgs LayoutFlag @@ -402,25 +418,12 @@ func (w *LinearLayout) updateLLHWidgetPos(wd Widget) { c.X += (w.getWeightedW(wd) / 2) - (wd.GetW() / 2) } } - c.Y = int((float64(w.h) / 2) - (float64(wd.GetH()) / 2)) - wd.SetPos(c) -} - -func (w *LinearLayout) updateWidgetPos(wd Widget) { - prevP, prevS := 0, 0 - for _, wrk := range w.widgets { - c := Coord{} - switch w.orientation { - case LinLayV: - - c.X, c.Y = 0, prevP+prevS - prevP, prevS = c.Y, wrk.GetH() - case LinLayH: - c.X, c.Y = prevP+prevS, 0 - prevP, prevS = c.X, wrk.GetW() - } - wd.SetPos(c) + if wd.GetW() < w.h { + c.Y = int((float64(w.h) / 2) - (float64(wd.GetH()) / 2)) + } else { + c.Y = 0 } + wd.SetPos(c) } func (w *LinearLayout) getWeightedH(wd Widget) int { @@ -431,8 +434,13 @@ func (w *LinearLayout) getWeightedH(wd Widget) int { if wght == 0 { wght = 1 } - ret := int(float64(w.h) * (float64(wght) / float64(w.totalWeight))) - return ret + retF := float64(w.h) * (float64(wght) / float64(w.totalWeight)) + retI := int(retF) + if retF-float64(retI) >= 0.5 { + return retI + 1 + } else { + return retI + } } func (w *LinearLayout) getWeightedW(wd Widget) int { @@ -443,7 +451,13 @@ func (w *LinearLayout) getWeightedW(wd Widget) int { if wght == 0 { wght = 1 } - return int(float64(w.w) * (float64(wght) / float64(w.totalWeight))) + retF := float64(w.w) * (float64(wght) / float64(w.totalWeight)) + retI := int(retF) + if retF-float64(retI) >= 0.5 { + return retI + 1 + } else { + return retI + } } func (w *LinearLayout) handleTab() bool { diff --git a/wdgt_searcher.go b/wdgt_searcher.go index bd18948..cec7411 100644 --- a/wdgt_searcher.go +++ b/wdgt_searcher.go @@ -84,6 +84,7 @@ func (w *Searcher) Init(id string, style tcell.Style) { func (w *Searcher) Id() string { return w.id } func (w *Searcher) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() + w.Log("Searcher<%s>.HandleResize: %d,%d", w.Id(), w.w, w.h) // Remove 2 from each for borders aW, aH := w.w-2, w.h-2 w.search.SetPos(Coord{X: 1, Y: 1}) @@ -172,10 +173,7 @@ func (w *Searcher) Draw(screen tcell.Screen) { if !w.visible { return } - dStyle := w.style - if !w.active { - dStyle = dStyle.Dim(true) - } + dStyle := w.style.Dim(!w.active) if len(w.title) > 0 { wh.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, wh.BRD_CSIMPLE, dStyle, screen) } else { @@ -235,7 +233,7 @@ func (w *Searcher) SetPos(c Coord) { w.x, w.y = c.X, c.Y } func (w *Searcher) SetW(x int) { w.w = x } func (w *Searcher) SetH(y int) { w.h = y } func (w *Searcher) GetW() int { return w.w } -func (w *Searcher) GetH() int { return w.y } +func (w *Searcher) GetH() int { return w.h } func (w *Searcher) SetSize(c Coord) { w.w, w.h = c.X, c.Y } func (w *Searcher) Focusable() bool { return true } func (w *Searcher) SetTabbable(b bool) { w.tabbable = b } @@ -310,6 +308,6 @@ func (w *Searcher) ClearSearch() { func (w *Searcher) SetLogger(l func(string, ...any)) { w.logger = l } func (w *Searcher) Log(txt string, args ...any) { if w.logger != nil { - w.logger(txt, args) + w.logger(txt, args...) } } diff --git a/wdgt_top_menu_layout.go b/wdgt_top_menu_layout.go index f8ba452..54ebee7 100644 --- a/wdgt_top_menu_layout.go +++ b/wdgt_top_menu_layout.go @@ -48,7 +48,7 @@ type TopMenuLayout struct { var _ Widget = (*TopMenuLayout)(nil) func NewTopMenuLayout(id string, s tcell.Style) *TopMenuLayout { - ret := &TopMenuLayout{} + ret := &TopMenuLayout{widget: NewBlankWidget(id)} ret.Init(id, s) return ret }