turbo-editors/turbo-corepublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

listbox.go · 190 lines · 4.6 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1package ui
2
3import (
4 "github.com/gdamore/tcell/v2"
5
📦 Turbo Core f3ade8d k33g 11h ago6 "rickub.com/turbo-editors/turbo-core/theme"
🛟 Updated. 28d5985 k33g 18h ago7)
8
9// ListBox is a scrolling list of strings with one selected line.
10//
11// It is what the Open File dialog, the theme picker and the window list are
12// all made of, so it carries the scrolling and the keyboard handling those
13// three would otherwise each repeat.
14type ListBox struct {
15 FocusBox
16 items []string
17 selected int
18 top int
19
20 // OnChoose is called when a line is picked with Enter or a double click.
21 OnChoose func(index int)
22 // OnSelect is called whenever the highlighted line changes.
23 OnSelect func(index int)
24}
25
26// NewListBox returns a list showing items, with the first one selected.
27func NewListBox(items []string) *ListBox {
28 return &ListBox{items: items}
29}
30
31// Items returns the lines the list holds.
32func (l *ListBox) Items() []string { return l.items }
33
34// SetItems replaces the lines and selects the first one.
35func (l *ListBox) SetItems(items []string) {
36 l.items = items
37 l.top = 0
38 l.setSelected(0)
39}
40
41// Selected returns the index of the highlighted line, or -1 when the list is
42// empty.
43func (l *ListBox) Selected() int {
44 if len(l.items) == 0 {
45 return -1
46 }
47 return l.selected
48}
49
50// SelectedItem returns the highlighted line, or the empty string when the list
51// is empty.
52func (l *ListBox) SelectedItem() string {
53 if index := l.Selected(); index >= 0 {
54 return l.items[index]
55 }
56 return ""
57}
58
59// Select highlights a line, clamping the index into the list.
60func (l *ListBox) Select(index int) { l.setSelected(index) }
61
62// setSelected moves the highlight and tells the owner, if it changed.
63func (l *ListBox) setSelected(index int) {
64 if len(l.items) == 0 {
65 l.selected = 0
66 return
67 }
68
69 index = min(max(index, 0), len(l.items)-1)
70 if index == l.selected {
71 return
72 }
73 l.selected = index
74 if l.OnSelect != nil {
75 l.OnSelect(index)
76 }
77}
78
79// Choose runs the OnChoose action for the highlighted line.
80func (l *ListBox) Choose() {
81 if l.OnChoose != nil && l.Selected() >= 0 {
82 l.OnChoose(l.selected)
83 }
84}
85
86// visibleHeight is how many lines fit, one column being kept for the scroll
87// bar down the right-hand edge.
88func (l *ListBox) visibleHeight() int { return l.Bounds().H }
89
90// scrollToSelection slides the view so the highlighted line is inside it.
91func (l *ListBox) scrollToSelection() {
92 height := l.visibleHeight()
93 if height <= 0 {
94 return
95 }
96
97 l.top = min(l.top, l.selected)
98 if l.selected >= l.top+height {
99 l.top = l.selected - height + 1
100 }
101 l.top = min(max(l.top, 0), max(len(l.items)-height, 0))
102}
103
104// Draw paints the visible lines and the scroll bar.
105func (l *ListBox) Draw(p *Painter, th *theme.Theme) {
106 area := p.Sub(l.Bounds())
107 normal := th.Style(theme.KeyList)
108 area.Clear(normal)
109
110 l.scrollToSelection()
111 width := max(area.Size().W-1, 0)
112
113 for row := range area.Size().H {
114 index := l.top + row
115 if index >= len(l.items) {
116 break
117 }
118
119 style := normal
120 if index == l.selected {
121 style = l.selectionStyle(th)
122 area.HLine(0, row, width, ' ', style)
123 }
124 area.TextLimited(0, row, width, l.items[index], style)
125 }
126
127 DrawVScrollBar(area, area.Size().W-1, 0, area.Size().H,
128 l.top, area.Size().H, len(l.items),
129 th.Style(theme.KeyScrollBar), th.Style(theme.KeyScrollBarThumb))
130}
131
132// selectionStyle is the highlight, dimmed when the list does not have the
133// focus so that a dialog shows where the keyboard actually is.
134func (l *ListBox) selectionStyle(th *theme.Theme) tcell.Style {
135 if l.Focused() {
136 return th.Style(theme.KeyListSelected)
137 }
138 return th.Style(theme.KeyListUnfocused)
139}
140
141// HandleKey walks the list.
142func (l *ListBox) HandleKey(ev *tcell.EventKey) bool {
143 if !l.Focused() {
144 return false
145 }
146
147 height := max(l.visibleHeight(), 1)
148 switch ev.Key() {
149 case tcell.KeyUp:
150 l.setSelected(l.selected - 1)
151 case tcell.KeyDown:
152 l.setSelected(l.selected + 1)
153 case tcell.KeyPgUp:
154 l.setSelected(l.selected - height)
155 case tcell.KeyPgDn:
156 l.setSelected(l.selected + height)
157 case tcell.KeyHome:
158 l.setSelected(0)
159 case tcell.KeyEnd:
160 l.setSelected(len(l.items) - 1)
161 case tcell.KeyEnter:
162 l.Choose()
163 default:
164 return false
165 }
166 return true
167}
168
169// HandleMouse selects the clicked line, and chooses it on a second click or a
170// wheel-free double click.
171func (l *ListBox) HandleMouse(ev *tcell.EventMouse) bool {
172 if !l.hit(ev) {
173 return false
174 }
175
176 switch ev.Buttons() {
177 case tcell.WheelUp:
178 l.setSelected(l.selected - 1)
179 case tcell.WheelDown:
180 l.setSelected(l.selected + 1)
181 case tcell.Button1:
182 _, y := ev.Position()
183 index := l.top + y - l.Bounds().Y
184 if index == l.selected {
185 l.Choose() // clicking the already-selected line opens it
186 }
187 l.setSelected(index)
188 }
189 return true
190}