diff --git a/elements/combobox.go b/elements/combobox.go index 2301220..5666906 100644 --- a/elements/combobox.go +++ b/elements/combobox.go @@ -8,6 +8,7 @@ import "git.tebibyte.media/sashakoshka/tomo/default/theme" import "git.tebibyte.media/sashakoshka/tomo/default/config" import "git.tebibyte.media/sashakoshka/tomo/textdraw" +// Option specifies a ComboBox option. A blank option will display as "(None)". type Option string func (option Option) Title () string { @@ -18,7 +19,7 @@ func (option Option) Title () string { } } -// Button is a clickable button. +// ComboBox is an input that can be one of several predetermined values. type ComboBox struct { entity tomo.FocusableEntity drawer textdraw.Drawer @@ -35,7 +36,7 @@ type ComboBox struct { onChange func () } -// NewButton creates a new button with the specified label text. +// NewComboBox creates a new ComboBox with the specifed options. func NewComboBox (options ...Option) (element *ComboBox) { if len(options) == 0 { options = []Option { "" } } element = &ComboBox { enabled: true, options: options } @@ -98,15 +99,17 @@ func (element *ComboBox) Draw (destination canvas.Canvas) { element.drawer.Draw(destination, foreground, offset) } -// OnClick sets the function to be called when the button is clicked. +// OnChange sets the function to be called when this element's value is changed. func (element *ComboBox) OnChange (callback func ()) { element.onChange = callback } +// Value returns this element's value. func (element *ComboBox) Value () Option { return element.selected } +// Select sets this element's value. func (element *ComboBox) Select (option Option) { element.selected = option element.drawer.SetText([]rune(option.Title())) @@ -117,6 +120,7 @@ func (element *ComboBox) Select (option Option) { } } +// Filled returns whether this element has a value other than (None). func (element *ComboBox) Filled () bool { return element.selected != "" } @@ -126,12 +130,12 @@ func (element *ComboBox) Focus () { if !element.entity.Focused() { element.entity.Focus() } } -// Enabled returns whether this button is enabled or not. +// Enabled returns whether this element is enabled or not. func (element *ComboBox) Enabled () bool { return element.enabled } -// SetEnabled sets whether this button can be clicked or not. +// SetEnabled sets whether this element is enabled or not. func (element *ComboBox) SetEnabled (enabled bool) { if element.enabled == enabled { return } element.enabled = enabled @@ -180,6 +184,7 @@ func (element *ComboBox) HandleMouseUp ( func (element *ComboBox) HandleKeyDown (key input.Key, modifiers input.Modifiers) { if !element.Enabled() { return } + // TODO: use arrow keys to cycle options if key == input.KeyEnter { element.pressed = true element.entity.Invalidate() @@ -200,18 +205,25 @@ func (element *ComboBox) dropDown () { menu, err := window.NewMenu(element.entity.Bounds()) if err != nil { return } + cellToOption := make(map[tomo.Selectable] Option) + list := NewList() for _, option := range element.options { option := option cell := NewCell(NewLabel(option.Title())) - cell.OnSelectionChange(func () { - if cell.Selected() { - element.Select(option) - menu.Close() - } - }) + cellToOption[cell] = option list.Adopt(cell) + + if option == element.selected { + list.Select(cell) + } } + list.OnClick(func () { + selected := list.Selected() + if selected == nil { return } + element.Select(cellToOption[selected]) + menu.Close() + }) menu.Adopt(list) list.Focus() diff --git a/elements/list.go b/elements/list.go index 2ef3f4f..ac94eba 100644 --- a/elements/list.go +++ b/elements/list.go @@ -26,7 +26,8 @@ type List struct { forcedMinimumHeight int theme theme.Wrapped - + + onClick func () onScrollBoundsChange func () } @@ -92,6 +93,22 @@ func (element *List) Layout () { } } +func (element *List) Selected () tomo.Selectable { + if element.selected == -1 { return nil } + child, ok := element.entity.Child(element.selected).(tomo.Selectable) + if !ok { return nil } + return child +} + +func (element *List) Select (child tomo.Selectable) { + index := element.entity.IndexOf(child) + if element.selected == index { return } + element.selectNone() + element.selected = index + element.entity.SelectChild(index, true) + element.scrollToSelected() +} + func (element *List) Enabled () bool { return element.enabled } @@ -135,12 +152,7 @@ func (element *List) HandleChildMouseDown ( if !element.enabled { return } element.Focus() if child, ok := child.(tomo.Selectable); ok { - index := element.entity.IndexOf(child) - if element.selected == index { return } - element.selectNone() - element.selected = index - element.entity.SelectChild(index, true) - element.scrollToSelected() + element.Select(child) } } @@ -149,7 +161,12 @@ func (element *List) HandleChildMouseUp ( button input.Button, modifiers input.Modifiers, child tomo.Element, -) { } +) { + if !position.In(child.Entity().Bounds()) { return } + if element.onClick != nil { + element.onClick() + } +} func (element *List) HandleChildFlexibleHeightChange (child tomo.Flexible) { element.updateMinimumSize() @@ -165,6 +182,10 @@ func (element *List) HandleKeyDown (key input.Key, modifiers input.Modifiers) { index = element.selected - 1 case input.KeyDown, input.KeyRight: index = element.selected + 1 + case input.KeyEnter: + if element.onClick != nil { + element.onClick() + } } if index >= 0 && index < element.entity.CountChildren() { element.selectNone() @@ -245,6 +266,10 @@ func (element *List) OnScrollBoundsChange (callback func ()) { element.onScrollBoundsChange = callback } +func (element *List) OnClick (callback func ()) { + element.onClick = callback +} + // ScrollAxes returns the supported axes for scrolling. func (element *List) ScrollAxes () (horizontal, vertical bool) { return false, true