diff --git a/layout_flags.go b/layout_flags.go index a088860..02f0eb5 100644 --- a/layout_flags.go +++ b/layout_flags.go @@ -44,22 +44,55 @@ const ( ) const ( - LFAlignTopLeft = LayoutFlag(LFAlignHLeft | LFAlignVTop) - LFAlignCenter = LayoutFlag(LFAlignHCenter | LFAlignVCenter) + LFAlignAll = LayoutFlag(0x1111) ) -func (f LayoutFlag) Add(fl LayoutFlag) { f |= fl } +const ( + LFAlignTopLeft = LayoutFlag(LFAlignHLeft | LFAlignVTop) + LFAlignTop = LayoutFlag(LFAlignHCenter | LFAlignVTop) + LFAlignTopRight = LayoutFlag(LFAlignHRight | LFAlignVTop) + LFAlignLeft = LayoutFlag(LFAlignHLeft | LFAlignVCenter) + LFAlignCenter = LayoutFlag(LFAlignHCenter | LFAlignVCenter) + LFAlignRight = LayoutFlag(LFAlignHRight | LFAlignVCenter) + LFAlignBottomLeft = LayoutFlag(LFAlignHLeft | LFAlignVBottom) + LFAlignBottom = LayoutFlag(LFAlignHCenter | LFAlignVBottom) + LFAlignBottomRight = LayoutFlag(LFAlignHRight | LFAlignVBottom) +) + +// Size Flags +// The default should be MinW, MinH +const ( + _ = LayoutFlag(iota << 4) + LFSizeFullW // 00010000 + LFSizeFullH // 00100000 + LFSizeFull // 00110000 + + _ = LayoutFlag(iota << 6) + LFSizeWrapW // 01000000 + LFSizeWrapH // 10000000 + LFSizeWrap // 11000000 + + LFSizeAll = LayoutFlag(0x11110000) +) + +func (f LayoutFlag) Add(fl LayoutFlag) { f |= fl } +func (f LayoutFlag) ClearAll() { + f.ClearAllAlign() + f.ClearAllSize() +} + +// Alignment Manipulation func (f LayoutFlag) AlignH() LayoutFlag { return f & LFAlignH } func (f LayoutFlag) AlignV() LayoutFlag { return f & LFAlignV } func (f LayoutFlag) IsAlignH() bool { return f.AlignH() > 0 } func (f LayoutFlag) IsAlignV() bool { return f.AlignV() > 0 } func (f LayoutFlag) SetTopLeft() { - f.ClearAll() + f.ClearAllAlign() f = LFAlignHLeft | LFAlignVTop } func (f LayoutFlag) SetBottomCenter() { - f.ClearAll() + f.ClearAllAlign() f = LFAlignHCenter | LFAlignVBottom } @@ -72,8 +105,26 @@ func (f LayoutFlag) ClearAlignV() { f = f &^ LFAlignV f.Add(LFAlignVCenter) } - -func (f LayoutFlag) ClearAll() { +func (f LayoutFlag) ClearAllAlign() { f.ClearAlignH() f.ClearAlignV() } + +// Size Manipulation +func (f LayoutFlag) SetSizeWrap() { + f.ClearAllSize() + f = f | LFSizeWrap +} +func (f LayoutFlag) SetSizeFull() { + f.ClearAllSize() + f = f | LFSizeFull +} +func (f LayoutFlag) ClearAllSize() { + f = f &^ LFSizeAll +} +func (f LayoutFlag) ClearSizeW() { + f = f &^ (LFSizeFullW | LFSizeWrapW) +} +func (f LayoutFlag) ClearSizeH() { + f = f &^ (LFSizeFullH | LFSizeWrapH) +} diff --git a/size.go b/size.go new file mode 100644 index 0000000..eb10e5c --- /dev/null +++ b/size.go @@ -0,0 +1,27 @@ +/* +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 + +type Size struct { + W, H int + flags LayoutFlag +} diff --git a/wdgt_absolute_layout.go b/wdgt_absolute_layout.go index 6a9ac39..d2b0bc1 100644 --- a/wdgt_absolute_layout.go +++ b/wdgt_absolute_layout.go @@ -30,15 +30,16 @@ type AbsoluteLayout struct { id string style tcell.Style - x, y int - w, h int - bordered bool - widgets []Widget - wCoords map[Widget]Coord - wAnchor map[Widget]AbsoluteAnchor - wManualSizes map[Widget]Coord + x, y int + w, h int + bordered bool + widgets []Widget + wCoords map[Widget]Coord + wAnchor map[Widget]LayoutFlag + wManualSizes map[Widget]Coord + wDynamicSizes map[Widget]LayoutFlag - defAnchor AbsoluteAnchor + defAnchor LayoutFlag active bool visible bool @@ -52,21 +53,6 @@ type AbsoluteLayout struct { logger func(string) } -type AbsoluteAnchor int - -const ( - AnchorTL = AbsoluteAnchor(iota) // widget starts at , - AnchorT // widget starts at -, - AnchorTR // widget starts at , - AnchorL // widget starts at , - AnchorC // widget starts at -, - - AnchorR // widget starts at -, - - AnchorBL // widget starts at , - - AnchorB // widget starts at -, - - AnchorBR // widget starts at -, - - AnchorErr -) - var _ Widget = (*AbsoluteLayout)(nil) func NewAbsoluteLayout(id string, s tcell.Style) *AbsoluteLayout { @@ -79,11 +65,12 @@ func (w *AbsoluteLayout) Init(id string, s tcell.Style) { w.id = id w.style = s w.visible = true - w.defAnchor = AnchorTL + w.defAnchor = LFAlignCenter w.keyMap = BlankKeyMap() w.wCoords = make(map[Widget]Coord) - w.wAnchor = make(map[Widget]AbsoluteAnchor) + w.wAnchor = make(map[Widget]LayoutFlag) w.wManualSizes = make(map[Widget]Coord) + w.wDynamicSizes = make(map[Widget]LayoutFlag) w.focusable = true } @@ -207,7 +194,7 @@ func (w *AbsoluteLayout) MinH() int { // Add a widget at x/y func (w *AbsoluteLayout) Add(n Widget, pos Coord) { w.AddAnchored(n, pos, w.defAnchor) } -func (w *AbsoluteLayout) AddAnchored(n Widget, pos Coord, anchor AbsoluteAnchor) { +func (w *AbsoluteLayout) AddAnchored(n Widget, pos Coord, anchor LayoutFlag) { w.widgets = append(w.widgets, n) w.wCoords[n] = pos w.wAnchor[n] = anchor @@ -220,12 +207,7 @@ func (w *AbsoluteLayout) Clear() { w.wCoords = make(map[Widget]Coord) } -func (w *AbsoluteLayout) SetDefaultAnchor(d AbsoluteAnchor) { - if d < 0 || d > AnchorErr { - d = AnchorTL - } - w.defAnchor = d -} +func (w *AbsoluteLayout) SetDefaultAnchor(d LayoutFlag) { w.defAnchor = d } func (w *AbsoluteLayout) SetLogger(l func(string)) { w.logger = l } func (w *AbsoluteLayout) Log(txt string) { @@ -238,11 +220,26 @@ func (w *AbsoluteLayout) SetBordered(b bool) { w.bordered = b } func (w *AbsoluteLayout) updateWidgetLayouts() { // In an Absolute Layout, widgets are given a definite position and anchor. - // The anchor is a side of the layout (see AbsoluteAnchor type) + // The anchor is a side of the layout (see LayoutFlag type) + + // Process: + // 1. Update pos of all widgets + // 2. Update manually sized widgets + // 3. Update dynamically sized widgets for _, wd := range w.widgets { w.updateWidgetPos(wd) + } + for wd := range w.wManualSizes { w.updateWidgetSize(wd) } + for wd := range w.wDynamicSizes { + w.updateWidgetSize(wd) + } +} + +// Set a widgets position relative to the layout +func (w *AbsoluteLayout) updateWidgetPos(wd Widget) { + wd.SetPos(w.getRelPos(wd)) } func (w *AbsoluteLayout) updateWidgetSize(wd Widget) { @@ -274,21 +271,13 @@ func (w *AbsoluteLayout) updateWidgetSize(wd Widget) { } } -// Set a widgets position relative to the layout -func (w *AbsoluteLayout) updateWidgetPos(wd Widget) { - wd.SetPos(w.getRelPos(wd)) -} - // Manually set the size of a widget, the Layout won't override it -func (w *AbsoluteLayout) SetWidgetSize(wd Widget, sz Coord) { w.wManualSizes[wd] = sz } - -func (w *AbsoluteLayout) ShrinkWrap(wd Widget) { - w.SetWidgetSize(wd, Coord{X: wd.MinW(), Y: wd.MinH()}) -} +func (w *AbsoluteLayout) SetWidgetSize(wd Widget, sz Coord) { w.wManualSizes[wd] = sz } +func (w *AbsoluteLayout) SetDynamicWidgetSize(wd Widget, flg LayoutFlag) { w.wDynamicSizes[wd] = flg } func (w *AbsoluteLayout) getRelPos(wd Widget) Coord { var p Coord - var a AbsoluteAnchor + var a LayoutFlag var ok bool if p, ok = w.wCoords[wd]; !ok { // Default to top-left corner @@ -306,32 +295,32 @@ func (w *AbsoluteLayout) getRelPos(wd Widget) Coord { } midX, midY := (w.w / 2), (w.h / 2) switch a { - case AnchorTL: + case LFAlignTopLeft: return p.Add(Coord{X: leftX, Y: topY}) - case AnchorT: + case LFAlignTop: return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: topY}) - case AnchorTR: + case LFAlignTopRight: return p.Add(Coord{X: rightX - wd.GetW(), Y: topY}) - case AnchorL: + case LFAlignLeft: return p.Add(Coord{X: leftX, Y: midY - (wd.GetH() / 2)}) - case AnchorC: + case LFAlignCenter: return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: midY - (wd.GetH() / 2)}) - case AnchorR: + case LFAlignRight: return p.Add(Coord{X: rightX - wd.GetW(), Y: midY - (wd.GetH() / 2)}) - case AnchorBR: - return p.Add(Coord{X: rightX - wd.GetW(), Y: bottomY - wd.GetH()}) + case LFAlignBottomLeft: + return p.Add(Coord{X: leftX, Y: bottomY - wd.GetH()}) - case AnchorB: + case LFAlignBottom: return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: bottomY - wd.GetH()}) - case AnchorBL: - return p.Add(Coord{X: leftX, Y: bottomY - wd.GetH()}) + case LFAlignBottomRight: + return p.Add(Coord{X: rightX - wd.GetW(), Y: bottomY - wd.GetH()}) } return p @@ -341,3 +330,55 @@ func (w *AbsoluteLayout) getAbsPos(wd Widget) Coord { rel := w.getRelPos(wd) return rel.Add(Coord{X: w.x, Y: w.y}) } + +func (w *AbsoluteLayout) DeleteIndex(idx int) { + if idx < len(w.widgets) { + p := w.widgets[idx] + w.widgets = append(w.widgets[:idx], w.widgets[idx+1:]...) + delete(w.wCoords, p) + delete(w.wAnchor, p) + } +} +func (w *AbsoluteLayout) Delete(n Widget) { + for i := 0; i < len(w.widgets); i++ { + if w.widgets[i] == n { + w.DeleteIndex(i) + return + } + } +} +func (w *AbsoluteLayout) IndexOf(n Widget) int { + for i := range w.widgets { + if w.widgets[i] == n { + return i + } + } + return -1 +} + +func (w *AbsoluteLayout) ActivateWidget(n Widget) { + for i := range w.widgets { + w.widgets[i].SetActive(w.widgets[i] == n) + } +} + +func (w *AbsoluteLayout) FindById(id string) Widget { + for i := range w.widgets { + if w.widgets[i].Id() == id { + return w.widgets[i] + } + } + return nil +} + +func (w *AbsoluteLayout) Replace(n, with Widget) { + idx := w.IndexOf(n) + if idx == -1 { + // 'n' isn't in layout. Bail out. + return + } + coords := w.wCoords[n] + anchor := w.wAnchor[n] + w.Delete(n) + w.AddAnchored(with, coords, anchor) +} diff --git a/wdgt_linear_layout.go b/wdgt_linear_layout.go index bd2f967..f9725be 100644 --- a/wdgt_linear_layout.go +++ b/wdgt_linear_layout.go @@ -328,6 +328,14 @@ func (w *LinearLayout) IndexOf(n Widget) int { return -1 } +func (w *LinearLayout) FindById(id string) Widget { + for i := range w.widgets { + if w.widgets[i].Id() == id { + return w.widgets[i] + } + } + return nil +} func (w *LinearLayout) Contains(n Widget) bool { return w.IndexOf(n) >= 0 } @@ -344,6 +352,13 @@ func (w *LinearLayout) Replace(n, with Widget) { w.layoutFlags[with], w.layoutWeights[with] = pFlags, pWeight } +func (w *LinearLayout) AddAll(n Widget, more ...Widget) { + w.Add(n) + for i := range more { + w.Add(more[i]) + } +} + func (w *LinearLayout) Add(n Widget) { if w.Contains(n) { // If the widget is already in the layout, move it to the end diff --git a/wdgt_searcher.go b/wdgt_searcher.go index 2304fdf..4e75f7f 100644 --- a/wdgt_searcher.go +++ b/wdgt_searcher.go @@ -47,6 +47,7 @@ type Searcher struct { disableBorder bool selectFunc func(idx int, s string) bool + onChange func(idx int, s string) bool hideOnSelect bool logger func(string, ...any) @@ -116,11 +117,16 @@ func (w *Searcher) HandleKey(ev *tcell.EventKey) bool { if !w.active { return false } + sel := w.cursor b1, b2 := w.keyMap.Handle(ev), w.customKeyMap.Handle(ev) - if b1 || b2 { - return true + var ret bool + if !b1 && !b2 { + ret = w.search.HandleKey(ev) } - return w.search.HandleKey(ev) + if w.cursor != sel && w.onChange != nil { + w.onChange(w.cursor, w.filteredData[w.cursor]) + } + return b1 || b2 || ret } func (w *Searcher) handleKeyUp(ev *tcell.EventKey) bool { @@ -209,11 +215,11 @@ 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) } - } else if len(w.title) > 0 { - // TODO: Output the label } - w.GetPos().DrawOffset(w.search, screen) + //w.GetPos().DrawOffset(w.search, screen) x, y := w.x+1, w.y+2 + w.search.SetPos(Coord{X: w.x + 1, Y: w.y + 1}) + w.search.Draw(screen) var stIdx int if w.cursor > w.h/2 { stIdx = w.cursor - (w.h / 2) @@ -285,7 +291,6 @@ func (w *Searcher) MinH() int { func (w *Searcher) SetHideOnSelect(t bool) { w.hideOnSelect = t } func (w *Searcher) SetTitle(ttl string) { w.title = ttl } func (w *Searcher) SetData(data []string) { - // w.data = data w.data = data w.filteredData = data w.updateFilter() @@ -318,22 +323,31 @@ func (w *Searcher) updateFilter() { return } } + // If we're here, then the selected value changed if w.cursor > len(w.filteredData) { w.cursor = len(w.filteredData) - 1 + w.doChange() return } w.cursor = 0 + w.doChange() } func (w *Searcher) SelectedValue() string { return w.filteredData[w.cursor] } func (w *Searcher) SelectedIndex() int { return w.cursor } -func (w *Searcher) SetSearchValue(val string) { - w.search.SetValue(val) -} +func (w *Searcher) SetSearchValue(val string) { w.search.SetValue(val) } -func (w *Searcher) SetSelectFunc(f func(idx int, s string) bool) { - w.selectFunc = f +func (w *Searcher) SetSelectFunc(f func(idx int, s string) bool) { w.selectFunc = f } +func (w *Searcher) SetOnChange(f func(idx int, s string) bool) { w.onChange = f } +func (w *Searcher) doChange() { + l := len(w.filteredData) + if w.onChange == nil || l == 0 { + return + } + if w.cursor < l { + w.onChange(w.cursor, w.filteredData[w.cursor]) + } } func (w *Searcher) ClearSearch() {