diff --git a/example/ui.go b/example/ui.go index 40747d3..6384706 100644 --- a/example/ui.go +++ b/example/ui.go @@ -92,7 +92,7 @@ func (ui *Ui) run() error { case *tcell.EventKey: if ev.Key() == tcell.KeyCtrlC { - ui.stop() + ui.Exit() return nil } ui.screen.HandleKey(ev) @@ -115,7 +115,7 @@ func (ui *Ui) SetScreen(scr *UiScreen) { ui.screen.HandleResize(tcell.NewEventResize(ui.GetSize())) } -func (ui *Ui) stop() { ui.running = false } +func (ui *Ui) Exit() { ui.running = false } func (ui *Ui) cleanup() { ui.tScreen.Fini() } const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" diff --git a/example/ui_screen.go b/example/ui_screen.go index d03aea8..f9609db 100644 --- a/example/ui_screen.go +++ b/example/ui_screen.go @@ -30,9 +30,11 @@ type UiScreen struct { ui *Ui w, h int - widget w.Widget - ll *w.LinearLayout - log *w.Cli + widget w.Widget + ll *w.LinearLayout + log *w.Cli + alert *w.Alert + alertLayout *w.LinearLayout widgets []w.Widget cursor int @@ -46,88 +48,25 @@ func (s *UiScreen) Init(ui *Ui) { 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") - var dat []string - for i := 0; i < 1000; i++ { - dat = append(dat, RandomString(15)) - } - searcher.SetData(dat) - searcher.SetSelectFunc(func(idx int, v string) bool { - s.log.Log("Searcher Value Selected: %s", v) - return true - }) - searcher.SetH(10) - hl.Add(searcher) - - db := w.NewButton("test-bd-1", ui.style) - db.SetLabel("Button 1") - db.SetOnPressed(func() bool { - s.log.Log("Button 1 Pressed") - return true - }) - hl.Add(db) - db = w.NewButton("test-bd-2", ui.style) - db.SetLabel("Button 2") - db.SetOnPressed(func() bool { - s.log.Log("Button 2 Pressed") - return true - }) - hl.Add(db) - s.ll.Add(hl) - - btnL := w.NewLinearLayout("test.btnll", ui.style) - btnL.SetTabbable(true) - btnL.SetLogger(s.log.Log) - btnL.SetOrientation(w.LinLayH) - - btnCancel := w.NewButton("btn-cancel", ui.style) - btnCancel.SetLabel("Cancel") - btnCancel.SetOnPressed(func() bool { - s.log.Log("Cancel Pressed") - return true - }) - btnL.Add(btnCancel) - btnL.AddFlag(btnCancel, w.LFAlignVBottom) - - btnOk := w.NewButton("btn-ok", ui.style) - btnOk.SetLabel("Ok") - btnOk.SetOnPressed(func() bool { - s.log.Log("Ok Pressed") - return true - }) - btnL.Add(btnOk) - btnL.AddFlag(btnOk, w.LFAlignVBottom) - s.ll.Add(btnL) - s.ll.AddFlag(btnL, w.LFAlignVBottom) - + tf := w.NewTimeField("time-field", ui.style) + tf.SetLabel("Time") + s.ll.Add(tf) ml := w.NewTopMenuLayout("menu", ui.style) m := ml.Menu() m.SetLabel("Widget Example") ml.AddMenuItems( m.CreateMenuItem("File", nil, m.CreateMenuItem("Exit", func() bool { - s.ui.stop() + ui.Exit() return true }), ), ) ml.SetLogger(s.log.Log) ml.SetWidget(s.ll) + ml.SetActive(true) s.widget = ml - /* - dw := w.NewDebugWidget("debug", ui.style) - dw.SetWidget(ll) - dw.SetSize(w.Coord{X: 20, Y: 30}) - dw.SetActive(true) - s.widget = dw - */ s.ll.Add(s.log) // s.widgets = append(s.widgets, ml) diff --git a/wdgt_alert.go b/wdgt_alert.go index 1199670..cd6e47d 100644 --- a/wdgt_alert.go +++ b/wdgt_alert.go @@ -41,6 +41,7 @@ type Alert struct { layout *LinearLayout title string message *Text + btnLayout *LinearLayout btnOk, btnCancel *Button keyMap KeyMap @@ -64,18 +65,18 @@ func (w *Alert) Init(id string, style tcell.Style) { w.message = NewText(fmt.Sprintf("%s-text", id), style) w.layout.Add(w.message) - btnLayout := NewLinearLayout("alertbtn-layout", tcell.StyleDefault) - btnLayout.SetOrientation(LinLayH) - w.layout.Add(btnLayout) + w.btnLayout = NewLinearLayout("alertbtn-layout", tcell.StyleDefault) + w.btnLayout.SetOrientation(LinLayH) + w.layout.Add(w.btnLayout) w.btnCancel = NewButton(fmt.Sprintf("%s-cancel", id), style) w.btnCancel.SetLabel("Cancel") - btnLayout.Add(w.btnCancel) + w.btnLayout.Add(w.btnCancel) w.btnOk = NewButton(fmt.Sprintf("%s-select", id), style) w.btnOk.SetLabel("Ok") w.btnOk.SetActive(true) - btnLayout.Add(w.btnOk) + w.btnLayout.Add(w.btnOk) w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{ tcell.KeyTab: w.SelectNext, @@ -90,9 +91,14 @@ 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()) + if w.w > w.MinW() { + w.w = w.MinW() + } + if w.h > w.MinH() { + w.h = w.MinH() + } // 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)) } @@ -107,13 +113,10 @@ func (w *Alert) Draw(screen tcell.Screen) { if !w.visible { return } - dS := w.style - if !w.active { - dS = dS.Dim(true) - } + dS := w.style.Dim(!w.active) - wh.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, wh.BRD_SIMPLE, w.style, screen) - w.GetPos().DrawOffset(w, screen) + wh.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, wh.BRD_SIMPLE, dS, screen) + w.GetPos().DrawOffset(w.layout, screen) } func (w *Alert) Active() bool { return w.active } @@ -144,12 +147,12 @@ func (w *Alert) WantH() int { // Borders + Buttons func (w *Alert) MinW() int { - return 2 + wh.Max(w.message.MinW(), (w.btnOk.MinW()+w.btnCancel.MinW())) + return 5 + wh.Max(w.message.MinW(), (w.btnOk.MinW()+w.btnCancel.MinW())) } // Borders + Buttons + 2 lines for message func (w *Alert) MinH() int { - return 2 + w.message.MinH() + w.btnOk.MinH() + return 5 + w.message.MinH() + w.btnOk.MinH() } func (w *Alert) SetTitle(ttl string) { w.title = ttl } @@ -180,6 +183,7 @@ func (w *Alert) Do(ev *tcell.EventKey) bool { func (w *Alert) SetLogger(l func(string, ...any)) { w.logger = l w.layout.SetLogger(l) + w.btnLayout.SetLogger(l) } func (w *Alert) Log(txt string, args ...any) { diff --git a/wdgt_linear_layout.go b/wdgt_linear_layout.go index 2cd9c88..a3d9dab 100644 --- a/wdgt_linear_layout.go +++ b/wdgt_linear_layout.go @@ -40,6 +40,8 @@ type LinearLayout struct { layoutFlags map[Widget]LayoutFlag layoutWeights map[Widget]int totalWeight int + // Stacked makes the layout ignore weights and effectively shrink-wrap all fields + stacked bool active bool visible bool @@ -81,14 +83,25 @@ func (w *LinearLayout) HandleResize(ev *tcell.EventResize) { } func (w *LinearLayout) HandleKey(ev *tcell.EventKey) bool { + w.Log("LinearLayout<%s>.HandleKey", w.Id()) + if !w.active { + w.Log("LinearLayout<%s>.HandleKey - NOPE. Not Active.", w.Id()) + return false + } + // First, see if the active widget handles the key + w.Log("LinearLayout<%s>.HandleKey: Checking Active Widget...", w.Id()) if len(w.widgets) > w.cursor { + w.Log("LinearLayout<%s>.HandleKey: Widget<%s>.HandleKey", w.Id(), w.widgets[w.cursor].Id()) if w.widgets[w.cursor].HandleKey(ev) { return true } + w.Log("LinearLayout<%s>.HandleKey: Widget<%s>.HandleKey Returns", w.Id(), w.widgets[w.cursor].Id()) } + w.Log("LinearLayout<%s>.HandleKey: Checking for Tab", w.Id()) if ev.Key() == tcell.KeyTab && !w.disableTab { + w.Log("LinearLayout<%s>.HandleKey: Handling Tab", w.Id()) return w.handleTab() } @@ -214,6 +227,18 @@ func (w *LinearLayout) Contains(n Widget) bool { return w.IndexOf(n) >= 0 } +func (w *LinearLayout) Replace(n, with Widget) { + idx := w.IndexOf(n) + if idx == -1 { + // 'n' isn't in layout. Bail out. + return + } + pFlags, pWeight := w.layoutFlags[n], w.layoutWeights[n] + w.Delete(n) + w.Insert(with, idx) + w.layoutFlags[with], w.layoutWeights[with] = pFlags, pWeight +} + func (w *LinearLayout) Add(n Widget) { if w.Contains(n) { // If the widget is already in the layout, move it to the end @@ -327,6 +352,9 @@ 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) { + if w.stacked { + return + } rW := w.w if wd == w.widgets[len(w.widgets)-1] { wrk := float64(w.w) / float64(w.totalWeight) @@ -338,6 +366,9 @@ func (w *LinearLayout) updateLLVWidgetSize(wd Widget) { } func (w *LinearLayout) updateLLHWidgetSize(wd Widget) { + if w.stacked { + return + } rH := w.h if wd == w.widgets[len(w.widgets)-1] { wrk := float64(w.h) / float64(w.totalWeight) @@ -461,6 +492,7 @@ func (w *LinearLayout) getWeightedW(wd Widget) int { } func (w *LinearLayout) handleTab() bool { + w.Log("%s - Handling Tab", w.Id()) beg := w.cursor // Find the next tabbable widget w.cursor = (w.cursor + 1) % len(w.widgets) @@ -471,6 +503,8 @@ func (w *LinearLayout) handleTab() bool { return w.cursor > 0 && w.cursor != beg } +func (w *LinearLayout) SetStacked(s bool) { w.stacked = s } + func (w *LinearLayout) SetLogger(l func(string, ...any)) { w.logger = l } func (w *LinearLayout) Log(txt string, args ...any) { if w.logger != nil { diff --git a/wdgt_text.go b/wdgt_text.go index e76ba61..9f743a8 100644 --- a/wdgt_text.go +++ b/wdgt_text.go @@ -58,8 +58,6 @@ 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()) } func (w *Text) HandleKey(ev *tcell.EventKey) bool { return false } func (w *Text) HandleTime(ev *tcell.EventTime) {} @@ -69,7 +67,7 @@ func (w *Text) Draw(screen tcell.Screen) { } y := w.y for i := range w.message { - wh.DrawText(w.x-(len(w.message[i])/2), y, w.message[i], w.style, screen) + wh.DrawText(w.x+(w.w/2)-(len(w.message[i])/2), y, w.message[i], w.style, screen) y++ } } diff --git a/wdgt_timefield.go b/wdgt_timefield.go index ed6991c..1511746 100644 --- a/wdgt_timefield.go +++ b/wdgt_timefield.go @@ -40,6 +40,9 @@ type TimeField struct { x, y int w, h int + yr, mo, dy int + hr, mn, sc int + value time.Time hasDate bool hasTime bool @@ -75,21 +78,34 @@ func (w *TimeField) Init(id string, style tcell.Style) { tcell.KeyHome: w.handleHome, tcell.KeyEnd: w.handleEnd, }) + w.visible = true w.tabbable = true } 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()) + if w.w > w.WantW() { + w.w = w.WantW() + } + if w.h > w.WantH() { + w.h = w.WantH() + } } func (w *TimeField) HandleKey(ev *tcell.EventKey) bool { if !w.active { return false } + switch w.cursor { + case 0: // year + case 1: // month + case 2: // day + case 3: // hour + case 4: // minute + case 5: // second + } + return false } func (w *TimeField) HandleTime(ev *tcell.EventTime) {} @@ -97,31 +113,33 @@ func (w *TimeField) Draw(screen tcell.Screen) { if !w.visible { return } - ds := w.style - if !w.active { - ds = ds.Dim(true) - } + dS := w.style.Dim(!w.active) + + // pos := w.GetPos() x := w.x + y := w.y labelW := len(w.label) if labelW > 0 { - wh.DrawText(w.x, w.y, w.label+": ", ds, screen) - x = x + labelW + 2 + wh.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.label, wh.BRD_SIMPLE, dS, screen) + } else { + wh.BorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, wh.BRD_SIMPLE, dS, screen) } + x, y = x+1, y+1 if w.hasDate { yr, mo, dy := w.value.Year(), w.value.Month(), w.value.Day() for idx, vl := range fmt.Sprintf("%4d%2d%2d", yr, mo, dy) { if idx == 4 || idx == 7 { - wh.DrawText(x, w.y, "-", ds, screen) + wh.DrawText(x, y, "-", dS, screen) x++ } if idx == w.cursor && !w.nowBtnActive { if w.active { - wh.DrawText(x, w.y, string(vl), ds.Reverse(true).Blink(true), screen) + wh.DrawText(x, y, string(vl), dS.Reverse(true).Blink(true), screen) } else { - wh.DrawText(x, w.y, string(vl), ds, screen) + wh.DrawText(x, y, string(vl), dS, screen) } } else { - wh.DrawText(x, w.y, string(vl), ds, screen) + wh.DrawText(x, y, string(vl), dS, screen) } x++ } @@ -134,26 +152,27 @@ func (w *TimeField) Draw(screen tcell.Screen) { } for idx, vl := range txt { if idx == 2 || idx == 5 { - wh.DrawText(x, w.y, ":", ds, screen) + wh.DrawText(x, y, ":", dS, screen) x++ } if idx+8 == w.cursor && !w.nowBtnActive { if w.active { - wh.DrawText(x, w.y, string(vl), ds.Reverse(true).Blink(true), screen) + wh.DrawText(x, y, string(vl), dS.Reverse(true).Blink(true), screen) } else { - wh.DrawText(x, w.y, string(vl), ds, screen) + wh.DrawText(x, y, string(vl), dS, screen) } } else { - wh.DrawText(x, w.y, string(vl), ds, screen) + wh.DrawText(x, y, string(vl), dS, screen) } x++ } } if w.showNowBtn { + x, y = x+w.w-8, y+1 if w.nowBtnActive { - wh.DrawText(x, w.y, "[ Now ]", ds.Reverse(true), screen) + wh.DrawText(x, y, "[ Now ]", dS.Reverse(true), screen) } else { - wh.DrawText(x, w.y, "[ Now ]", ds, screen) + wh.DrawText(x, y, "[ Now ]", dS, screen) } } } @@ -191,9 +210,9 @@ func (w *TimeField) WantW() int { wdt += 5 // hh:mm } } - return wdt + return wdt + 2 } -func (w *TimeField) WantH() int { return 1 } +func (w *TimeField) WantH() int { return 3 } func (w *TimeField) MinW() int { wdt := 0 if w.hasDate { @@ -209,9 +228,9 @@ func (w *TimeField) MinW() int { wdt += 5 // hh:mm } } - return wdt + return wdt + 2 } -func (w *TimeField) MinH() int { return 1 } +func (w *TimeField) MinH() int { return 3 } func (w *TimeField) SetLabel(lbl string) { w.label = lbl } func (w *TimeField) SetTime(tm time.Time) { w.value = tm } diff --git a/wdgt_top_menu_layout.go b/wdgt_top_menu_layout.go index 54ebee7..c30e5d1 100644 --- a/wdgt_top_menu_layout.go +++ b/wdgt_top_menu_layout.go @@ -62,6 +62,8 @@ func (w *TopMenuLayout) Init(id string, s tcell.Style) { w.menu.SetActive(false) w.menu.SetType(MenuTypeH) w.menu.SetTabbable(false) + + w.widget = NewBlankWidget("blank") } func (w *TopMenuLayout) Id() string { return w.id } @@ -84,6 +86,9 @@ func (w *TopMenuLayout) HandleKey(ev *tcell.EventKey) bool { } if ev.Key() == tcell.KeyEscape && w.menu != nil { w.menu.SetActive(!w.menu.Active()) + if w.widget != nil { + w.widget.SetActive(!w.menu.Active()) + } return true } if w.menu.Active() { @@ -92,6 +97,7 @@ func (w *TopMenuLayout) HandleKey(ev *tcell.EventKey) bool { // Pass the key through to the main widget if w.widget != nil { + w.Log(" Passing Key to Main Widget") return w.widget.HandleKey(ev) } return false @@ -106,12 +112,12 @@ func (w *TopMenuLayout) Draw(screen tcell.Screen) { if !w.visible { return } - if w.menu != nil { - w.GetPos().DrawOffset(w.menu, screen) - } if w.widget != nil { w.GetPos().DrawOffset(w.widget, screen) } + if w.menu != nil { + w.GetPos().DrawOffset(w.menu, screen) + } } func (w *TopMenuLayout) Active() bool { return w.active }