From f823a24fe84a37d216af62a4555252808d646311 Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Wed, 15 Oct 2025 16:25:18 -0500 Subject: [PATCH] Buffers ?! --- buffer.go | 140 ++++++++++++++++++++++++++++++++++++++++ cell.go | 41 ++++++++++++ wdgt_absolute_layout.go | 34 +++++++--- wdgt_checkbox.go | 14 ++-- wdgt_filepicker.go | 79 +++++++++++++++++------ wdgt_linear_layout.go | 1 + wdgt_menu.go | 1 + wdgt_menu_item.go | 24 +++++-- wdgt_relative_layout.go | 1 + wdgt_simple_list.go | 91 +++++++++++++++++++++++--- 10 files changed, 375 insertions(+), 51 deletions(-) create mode 100644 buffer.go create mode 100644 cell.go diff --git a/buffer.go b/buffer.go new file mode 100644 index 0000000..254c11a --- /dev/null +++ b/buffer.go @@ -0,0 +1,140 @@ +/* +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 ( + h "git.bullercodeworks.com/brian/tcell-widgets/helpers" + "github.com/gdamore/tcell" +) + +type Buffer struct { + // cells [][]Cell + cells map[Coord]Cell + maxX, maxY int + minX, minY int +} + +func NewBuffer() Buffer { + return Buffer{cells: make(map[Coord]Cell)} +} + +func (b *Buffer) Width() int { return b.maxX - b.minX } +func (b *Buffer) Height() int { return b.maxY - b.minY } + +func (b *Buffer) Draw(x, y int, screen tcell.Screen) { + for crd, cell := range b.cells { + cell.Draw(x+crd.X, y+crd.Y, screen) + } +} + +func (b *Buffer) SetCell(x, y int, c Cell) { + b.cells[Coord{X: x, Y: y}] = c + b.maxX, b.maxY = h.Max(b.maxX, x), h.Max(b.maxY, y) + b.minX, b.minY = h.Min(b.minX, x), h.Min(b.minY, y) +} + +func (b *Buffer) SetCells(x, y int, c [][]Cell) { + for i := range c { + for j := range c[i] { + b.SetCell(x+j, y+i, c[i][j]) + } + } +} + +// Buffer Helpers +func (b *Buffer) FillText(x, y int, txt string, style tcell.Style) { + for i := range txt { + b.SetCell(x+i, y, *NewCell(rune(txt[i]), style)) + } +} + +func (b *Buffer) Fill(x1, y1, x2, y2 int, r rune, style tcell.Style) { + if x1 > x2 { + x1, x2 = x2, x1 + } + if y1 > y2 { + y1, y2 = y2, y1 + } + for x := x1; x <= x2; x++ { + for y := y1; y <= y2; y++ { + b.SetCell(x, y, *NewCell(r, style)) + } + } +} + +func (b *Buffer) HRule(x1, x2, y int, rule []rune, s tcell.Style) { + b.Fill(x1, y, x2, y, rule[h.RULE_FILL], s) + b.SetCell(x1, y, *NewCell(rule[h.RULE_START], s)) + b.SetCell(x2, y, *NewCell(rule[h.RULE_END], s)) +} + +func (b *Buffer) VRule(x, y1, y2 int, rule []rune, s tcell.Style) { + b.Fill(x, y1, x, y2, rule[h.RULE_FILL], s) + b.SetCell(x, y1, *NewCell(rule[h.RULE_START], s)) + b.SetCell(x, y2, *NewCell(rule[h.RULE_END], s)) +} + +func (b *Buffer) BorderFilled(x1, y1, x2, y2 int, border []rune, s tcell.Style) { + b.Border(x1, y1, x2, y2, border, s) + b.Fill(x1+1, y1+1, x2-1, y2-1, ' ', s) +} + +func (b *Buffer) Border(x1, y1, x2, y2 int, border []rune, s tcell.Style) { + border = h.ValidateBorder(border) + b.Fill(x1+1, y1, x2-1, y1, border[h.BRD_N], s) + b.Fill(x2, y1+1, x2, y2-1, border[h.BRD_E], s) + b.Fill(x1+1, y2, x2-1, y2, border[h.BRD_S], s) + b.Fill(x1, y1+1, x1, y2-1, border[h.BRD_W], s) + b.SetCell(x1, y1, *NewCell(border[h.BRD_NW], s)) + b.SetCell(x2, y1, *NewCell(border[h.BRD_NE], s)) + b.SetCell(x1, y2, *NewCell(border[h.BRD_SW], s)) + b.SetCell(x2, y2, *NewCell(border[h.BRD_SE], s)) +} + +func (b *Buffer) TitledBorderFilled(x1, y1, x2, y2 int, ttl string, border []rune, s tcell.Style) { + b.TitledBorder(x1, y1, x2, y2, ttl, border, s) + b.Fill(x1+1, y1+1, x2-1, y2-1, ' ', s) +} + +func (b *Buffer) TitledBorder(x1, y1, x2, y2 int, ttl string, border []rune, s tcell.Style) { + if ttl == "" { + b.Border(x1, y1, x2, y2, border, s) + return + } + border = h.ValidateBorder(border) + ttlLength, maxTtlLength := len(ttl), (x2 - x1 - 2) + if ttlLength > maxTtlLength { + if maxTtlLength-3 > 0 { + ttlLength = maxTtlLength + ttl = ttl[0:maxTtlLength-3] + "..." + } + } + b.FillText(x1+1, y1, ttl, s) + b.Fill(x1+1+ttlLength, y1, x2-1, y1, border[h.BRD_N], s) + b.Fill(x2, y1+1, x2, y2-1, border[h.BRD_E], s) + b.Fill(x1+1, y2, x2-1, y2, border[h.BRD_S], s) + b.Fill(x1, y1+1, x1, y2-1, border[h.BRD_W], s) + b.SetCell(x1, y1, *NewCell(border[h.BRD_NW], s)) + b.SetCell(x2, y1, *NewCell(border[h.BRD_NE], s)) + b.SetCell(x1, y2, *NewCell(border[h.BRD_SW], s)) + b.SetCell(x2, y2, *NewCell(border[h.BRD_SE], s)) +} diff --git a/cell.go b/cell.go new file mode 100644 index 0000000..b56dd21 --- /dev/null +++ b/cell.go @@ -0,0 +1,41 @@ +/* +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 Cell struct { + r rune + combc []rune + style tcell.Style + empty bool +} + +func NewCell(r rune, style tcell.Style) *Cell { + return &Cell{r: r, style: style, empty: false} +} + +func (c *Cell) AddCombC(combc []rune) { c.combc = combc } + +func (c *Cell) Draw(x, y int, screen tcell.Screen) { + screen.SetContent(x, y, c.r, c.combc, c.style) +} diff --git a/wdgt_absolute_layout.go b/wdgt_absolute_layout.go index 5a69441..6a9ac39 100644 --- a/wdgt_absolute_layout.go +++ b/wdgt_absolute_layout.go @@ -22,6 +22,7 @@ THE SOFTWARE. package widgets import ( + wh "git.bullercodeworks.com/brian/tcell-widgets/helpers" "github.com/gdamore/tcell" ) @@ -31,6 +32,7 @@ type AbsoluteLayout struct { x, y int w, h int + bordered bool widgets []Widget wCoords map[Widget]Coord wAnchor map[Widget]AbsoluteAnchor @@ -151,6 +153,9 @@ func (w *AbsoluteLayout) Draw(screen tcell.Screen) { return } p := w.GetPos() + if w.bordered { + wh.Border(p.X, p.Y, p.X+w.w, p.Y+w.h, wh.BRD_CSIMPLE, w.style, screen) + } for _, wd := range w.widgets { p.DrawOffset(wd, screen) } @@ -229,6 +234,8 @@ func (w *AbsoluteLayout) Log(txt string) { } } +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) @@ -268,7 +275,9 @@ 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)) } +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 } @@ -288,34 +297,41 @@ func (w *AbsoluteLayout) getRelPos(wd Widget) Coord { if a, ok = w.wAnchor[wd]; !ok { a = w.defAnchor } + leftX, topY, rightX, bottomY := 0, 0, w.w, w.h + if w.bordered { + leftX += 1 + topY += 1 + rightX -= 1 + bottomY -= 1 + } midX, midY := (w.w / 2), (w.h / 2) switch a { case AnchorTL: - return p + return p.Add(Coord{X: leftX, Y: topY}) case AnchorT: - return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: 0}) + return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: topY}) case AnchorTR: - return p.Add(Coord{X: w.w - wd.GetW(), Y: 0}) + return p.Add(Coord{X: rightX - wd.GetW(), Y: topY}) case AnchorL: - return p.Add(Coord{X: 0, Y: midY - (wd.GetH() / 2)}) + return p.Add(Coord{X: leftX, Y: midY - (wd.GetH() / 2)}) case AnchorC: return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: midY - (wd.GetH() / 2)}) case AnchorR: - return p.Add(Coord{X: w.w - wd.GetW(), Y: midY - (wd.GetH() / 2)}) + return p.Add(Coord{X: rightX - wd.GetW(), Y: midY - (wd.GetH() / 2)}) case AnchorBR: - return p.Add(Coord{X: w.w - wd.GetW(), Y: w.h - wd.GetH()}) + return p.Add(Coord{X: rightX - wd.GetW(), Y: bottomY - wd.GetH()}) case AnchorB: - return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: w.h - wd.GetH()}) + return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: bottomY - wd.GetH()}) case AnchorBL: - return p.Add(Coord{X: 0, Y: w.h - wd.GetH()}) + return p.Add(Coord{X: leftX, Y: bottomY - wd.GetH()}) } return p diff --git a/wdgt_checkbox.go b/wdgt_checkbox.go index 43d4875..8c4ed49 100644 --- a/wdgt_checkbox.go +++ b/wdgt_checkbox.go @@ -63,12 +63,14 @@ func (w *Checkbox) Init(id string, style tcell.Style) { w.visible = true w.stateRunes = []rune{'X', ' ', '-'} w.focusable = true - w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{ - tcell.KeyEnter: w.ToggleState, - }) - w.AddToKeyMap(NewRuneMap(map[rune]func(ev *tcell.EventKey) bool{ - ' ': w.ToggleState, - })) + w.keyMap = KeyMap{ + Keys: map[tcell.Key]func(ev *tcell.EventKey) bool{ + tcell.KeyEnter: w.ToggleState, + }, + Runes: map[rune]func(ev *tcell.EventKey) bool{ + ' ': w.ToggleState, + }, + } w.customKeyMap = BlankKeyMap() } func (w *Checkbox) Id() string { return w.id } diff --git a/wdgt_filepicker.go b/wdgt_filepicker.go index 9042b84..99ad3d4 100644 --- a/wdgt_filepicker.go +++ b/wdgt_filepicker.go @@ -42,15 +42,16 @@ type FilePicker struct { w, h int wantW, wantH int - path string - wrkDir *os.File - - layout *RelativeLayout + path string fileList *SimpleList btnSelect, btnCancel *Button + error error + keyMap, customKeyMap KeyMap + + logger func(string, ...any) } var _ Widget = (*FilePicker)(nil) @@ -65,24 +66,34 @@ func (w *FilePicker) Init(id string, style tcell.Style) { w.id = id w.style = style - w.layout = NewRelativeLayout(fmt.Sprintf("%s-layout", id), style) + w.fileList = NewSimpleList(fmt.Sprintf("%s-files", id), style) + + w.btnCancel = NewButton(fmt.Sprintf("%s-cancel", id), style) + w.btnCancel.SetLabel("Cancel") w.btnSelect = NewButton(fmt.Sprintf("%s-select", id), style) w.btnSelect.SetLabel("Select") - w.layout.Add(w.btnSelect, nil, RelAncBR) - w.btnCancel = NewButton(fmt.Sprintf("%s-cancel", id), style) - w.btnCancel.SetLabel("Cancel") - w.layout.Add(w.btnCancel, nil, RelAncBL) + w.focusable = true + w.keyMap = BlankKeyMap() w.customKeyMap = BlankKeyMap() + + w.refreshFileList() } + func (w *FilePicker) Id() string { return w.id } func (w *FilePicker) HandleResize(ev *tcell.EventResize) { - w.w, w.h = ev.Size() - // ww, wh := w.w-2, w.h-2 // Trim border space - w.btnSelect.SetPos(Coord{X: w.x + w.w - w.btnSelect.WantW(), Y: w.y + w.h - 1}) - w.btnCancel.SetPos(Coord{X: w.x + 1, Y: w.y + w.h - 1}) + wd, ht := ev.Size() + w.SetW(wd) + w.SetH(ht) + w.fileList.SetPos(Coord{X: 0, Y: 0}) + w.fileList.HandleResize(Coord{X: wd, Y: ht - 3}.ResizeEvent()) + w.btnCancel.HandleResize(Coord{X: wd/2 - 1, Y: 3}.ResizeEvent()) + w.btnCancel.SetPos(Coord{X: 0, Y: ht - 3}) + w.btnSelect.HandleResize(Coord{X: wd/2 - 1, Y: 3}.ResizeEvent()) + w.btnSelect.SetPos(Coord{X: wd/2 + 1, Y: ht - 3}) + w.Log("%s: HandleResize(%d, %d)", w.id, wd, ht) } func (w *FilePicker) SetKeyMap(km KeyMap, def bool) { @@ -110,17 +121,21 @@ func (w *FilePicker) HandleKey(ev *tcell.EventKey) bool { b2 := w.customKeyMap.Handle(ev) return b1 || b2 } -func (w *FilePicker) HandleTime(ev *tcell.EventTime) { w.layout.HandleTime(ev) } + +func (w *FilePicker) HandleTime(ev *tcell.EventTime) { + w.fileList.HandleTime(ev) + w.btnCancel.HandleTime(ev) + w.btnSelect.HandleTime(ev) +} + func (w *FilePicker) Draw(screen tcell.Screen) { if !w.visible { return } - 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, ds, screen) - // TODO: Draw the file picker - wh.DrawText(w.x+1, w.y+1, "TODO: Draw Filepicker", ds, screen) - w.GetPos().DrawOffset(w.btnSelect, screen) - w.GetPos().DrawOffset(w.btnCancel, screen) + p := w.GetPos() + p.DrawOffset(w.fileList, screen) + p.DrawOffset(w.btnCancel, screen) + p.DrawOffset(w.btnSelect, screen) } func (w *FilePicker) Active() bool { return w.active } @@ -170,8 +185,8 @@ func (w *FilePicker) SetPath(path string) error { } else if !fs.IsDir() { return fmt.Errorf("path must be a directory") } - w.wrkDir = fl w.path = path + w.refreshFileList() return nil } @@ -182,3 +197,25 @@ func (w *FilePicker) SetOnSelect(sel func() bool) { func (w *FilePicker) SetOnCancel(cnc func() bool) { w.btnCancel.SetOnPressed(cnc) } + +func (w *FilePicker) refreshFileList() { + w.fileList.Clear() + if w.path == "" { + w.path = "./" + } + var entries []os.DirEntry + entries, w.error = os.ReadDir(w.path) + if w.error != nil { + return + } + for i := range entries { + w.fileList.Add(entries[i].Name()) + } +} + +func (w *FilePicker) SetLogger(l func(string, ...any)) { w.logger = l } +func (w *FilePicker) Log(txt string, args ...any) { + if w.logger != nil { + w.logger(txt, args...) + } +} diff --git a/wdgt_linear_layout.go b/wdgt_linear_layout.go index 2aebbe3..bd2f967 100644 --- a/wdgt_linear_layout.go +++ b/wdgt_linear_layout.go @@ -318,6 +318,7 @@ func (w *LinearLayout) ActivatePrev() bool { } func (w *LinearLayout) SetOrientation(o LinearLayoutOrient) { w.orientation = o } +func (w *LinearLayout) WidgetCount() int { return len(w.widgets) } func (w *LinearLayout) IndexOf(n Widget) int { for i := range w.widgets { if w.widgets[i] == n { diff --git a/wdgt_menu.go b/wdgt_menu.go index b6768d1..fa4a6e9 100644 --- a/wdgt_menu.go +++ b/wdgt_menu.go @@ -400,6 +400,7 @@ func (w *Menu) MoveDown(ev *tcell.EventKey) bool { func (w *Menu) CreateMenuItem(lbl string, do func() bool, hotKey rune, subItems ...*MenuItem) *MenuItem { d := NewMenuItem(fmt.Sprintf("menuitem-%s", lbl), tcell.StyleDefault) + d.SetMenuType(MenuTypeV) d.SetHotKey(hotKey) d.SetLabel(lbl) d.SetOnPressed(do) diff --git a/wdgt_menu_item.go b/wdgt_menu_item.go index cdba34d..0c49737 100644 --- a/wdgt_menu_item.go +++ b/wdgt_menu_item.go @@ -167,13 +167,21 @@ func (w *MenuItem) Draw(screen tcell.Screen) { wh.DrawText(w.x, y, fmt.Sprintf("╯%s╰", strings.Repeat(" ", len(w.label))), w.style, screen) x += 1 y += 1 - // pos := w.GetPos() - for i := range w.items { - // TODO: Use DrawOffset - // pos.DrawOffset(w.items[i], screen) - w.items[i].SetPos(Coord{X: x, Y: y}) - w.items[i].Draw(screen) - y++ + if len(w.items) > 0 { + if w.menuType == MenuTypeH { + for i := range w.items { + w.items[i].SetPos(Coord{X: x, Y: y}) + w.items[i].Draw(screen) + y++ + } + } else { + for i := range w.items { + ix := x + w.w + w.items[i].SetPos(Coord{X: ix, Y: y}) + w.items[i].Draw(screen) + y++ + } + } } } else { screen.SetContent(x, y, ' ', nil, st) @@ -326,3 +334,5 @@ func (w *MenuItem) FindItem(id string) *MenuItem { func (w *MenuItem) SetHotKey(r rune) { w.hotKey = r } func (w *MenuItem) GetHotKey() rune { return w.hotKey } + +func (w *MenuItem) SetMenuType(t MenuType) { w.menuType = t } diff --git a/wdgt_relative_layout.go b/wdgt_relative_layout.go index cdc4ecd..b3fbfae 100644 --- a/wdgt_relative_layout.go +++ b/wdgt_relative_layout.go @@ -76,6 +76,7 @@ func (w *RelativeLayout) Init(id string, style tcell.Style) { w.style = style w.visible = true w.focusable = true + w.widgetRelations = make(map[Widget][]widgetRelation) w.keyMap = BlankKeyMap() } func (w *RelativeLayout) Id() string { return w.id } diff --git a/wdgt_simple_list.go b/wdgt_simple_list.go index c81c304..03c9fee 100644 --- a/wdgt_simple_list.go +++ b/wdgt_simple_list.go @@ -72,16 +72,36 @@ func (w *SimpleList) Init(id string, style tcell.Style) { } return false }, + tcell.KeyPgDn: func(_ *tcell.EventKey) bool { return w.PageDn() }, + tcell.KeyPgUp: func(_ *tcell.EventKey) bool { return w.PageUp() }, }) w.keyMap.AddRune('j', func(ev *tcell.EventKey) bool { - if w.vimMode { - return w.MoveDown() + if !w.vimMode { + return false + } + return w.MoveDown() + }) + w.keyMap.AddRune('k', func(ev *tcell.EventKey) bool { + if !w.vimMode { + return false + } + return w.MoveUp() + }) + w.keyMap.AddRune('b', func(ev *tcell.EventKey) bool { + if !w.vimMode { + return false + } + if ev.Modifiers()&tcell.ModCtrl != 0 { + return w.PageUp() } return false }) - w.keyMap.AddRune('k', func(ev *tcell.EventKey) bool { - if w.vimMode { - return w.MoveUp() + w.keyMap.AddRune('f', func(ev *tcell.EventKey) bool { + if !w.vimMode { + return false + } + if ev.Modifiers()&tcell.ModCtrl != 0 { + return w.PageDn() } return false }) @@ -89,6 +109,7 @@ func (w *SimpleList) Init(id string, style tcell.Style) { w.itemsStyle = make(map[int]tcell.Style) w.focusable = true } + func (w *SimpleList) Id() string { return w.id } func (w *SimpleList) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() } @@ -134,7 +155,33 @@ func (w *SimpleList) Draw(screen tcell.Screen) { } } x, y = x+1, y+1 - for i := range w.list { + h := w.h - brdSz + ln := len(w.list) + st, ed := 0, ln-1 + if ln == 0 { + return + } + if ln > w.h-2 { + mid := h / 2 + if w.cursor < mid { + // List needs to begin at 0 + ed = h + 1 + } else if w.cursor > ln-mid { + // List needs to begin at ln-h + st = ln - h + 1 + } else { + st = w.cursor - mid + ed = st + h + 1 + } + } + // ed cannot be higher than ln-1 + if st < 0 { + st = 0 + } + if ed > ln-1 { + ed = ln - 1 + } + for i := st; i <= ed; i++ { rev := false if i == w.cursor { rev = true @@ -215,7 +262,7 @@ func (w *SimpleList) MoveUp() bool { } func (w *SimpleList) MoveDown() bool { - if w.cursor < len(w.list)-1 { + if w.cursor <= len(w.list)-2 { w.cursor++ if w.onChange != nil { w.onChange(w.cursor, w.list[w.cursor]) @@ -230,6 +277,28 @@ func (w *SimpleList) MoveDown() bool { } return false } + +func (w *SimpleList) PageUp() bool { + w.cursor -= w.h + if len(w.border) > 0 { + w.cursor += 2 + } + if w.cursor < 0 { + w.cursor = 0 + } + return true +} + +func (w *SimpleList) PageDn() bool { + w.cursor += w.h + if len(w.border) > 0 { + w.cursor -= 2 + } + if w.cursor > len(w.list)-2 { + w.cursor = len(w.list) - 2 + } + return true +} func (w *SimpleList) SetTitle(ttl string) { w.title = ttl } func (w *SimpleList) SetList(l []string) { w.list = l } func (w *SimpleList) Clear() { @@ -238,7 +307,11 @@ func (w *SimpleList) Clear() { delete(w.itemsStyle, k) } } -func (w *SimpleList) Add(l string) { w.list = append(w.list, l) } + +func (w *SimpleList) Add(l string) { + w.list = append(w.list, l) +} + func (w *SimpleList) Remove(l string) { var idx int var found bool @@ -289,4 +362,6 @@ func (w *SimpleList) Log(txt string, args ...any) { func (w *SimpleList) SetOnChange(c func(int, string) bool) { w.onChange = c } +func (w *SimpleList) GetSelectedItem() string { return w.list[w.cursor] } +func (w *SimpleList) GetAllItems() []string { return w.list } func (w *SimpleList) GetAllItemStyles() map[int]tcell.Style { return w.itemsStyle }