Hocus focus
This commit is contained in:
parent
35870951a2
commit
801c3ef6f5
@ -124,14 +124,14 @@ func (window *Window) handleKeyPress (
|
||||
modifiers.NumberPad = numberPad
|
||||
|
||||
if key == tomo.KeyTab && modifiers.Alt {
|
||||
if child, ok := window.child.(tomo.Selectable); ok {
|
||||
direction := tomo.SelectionDirectionForward
|
||||
if child, ok := window.child.(tomo.Focusable); ok {
|
||||
direction := tomo.KeynavDirectionForward
|
||||
if modifiers.Shift {
|
||||
direction = tomo.SelectionDirectionBackward
|
||||
direction = tomo.KeynavDirectionBackward
|
||||
}
|
||||
|
||||
if !child.HandleSelection(direction) {
|
||||
child.HandleDeselection()
|
||||
if !child.HandleFocus(direction) {
|
||||
child.HandleUnfocus()
|
||||
}
|
||||
}
|
||||
} else if child, ok := window.child.(tomo.KeyboardTarget); ok {
|
||||
|
@ -87,11 +87,11 @@ func (window *Window) Adopt (child tomo.Element) {
|
||||
if previousChild, ok := window.child.(tomo.Flexible); ok {
|
||||
previousChild.OnFlexibleHeightChange(nil)
|
||||
}
|
||||
if previousChild, ok := window.child.(tomo.Selectable); ok {
|
||||
previousChild.OnSelectionRequest(nil)
|
||||
previousChild.OnSelectionMotionRequest(nil)
|
||||
if previousChild.Selected() {
|
||||
previousChild.HandleDeselection()
|
||||
if previousChild, ok := window.child.(tomo.Focusable); ok {
|
||||
previousChild.OnFocusRequest(nil)
|
||||
previousChild.OnFocusMotionRequest(nil)
|
||||
if previousChild.Focused() {
|
||||
previousChild.HandleUnfocus()
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,8 +100,8 @@ func (window *Window) Adopt (child tomo.Element) {
|
||||
if newChild, ok := child.(tomo.Flexible); ok {
|
||||
newChild.OnFlexibleHeightChange(window.resizeChildToFit)
|
||||
}
|
||||
if newChild, ok := child.(tomo.Selectable); ok {
|
||||
newChild.OnSelectionRequest(window.childSelectionRequestCallback)
|
||||
if newChild, ok := child.(tomo.Focusable); ok {
|
||||
newChild.OnFocusRequest(window.childSelectionRequestCallback)
|
||||
}
|
||||
if child != nil {
|
||||
child.OnDamage(window.childDrawCallback)
|
||||
@ -282,20 +282,20 @@ func (window *Window) childMinimumSizeChangeCallback (width, height int) {
|
||||
}
|
||||
|
||||
func (window *Window) childSelectionRequestCallback () (granted bool) {
|
||||
if child, ok := window.child.(tomo.Selectable); ok {
|
||||
child.HandleSelection(tomo.SelectionDirectionNeutral)
|
||||
if child, ok := window.child.(tomo.Focusable); ok {
|
||||
child.HandleFocus(tomo.KeynavDirectionNeutral)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (window *Window) childSelectionMotionRequestCallback (
|
||||
direction tomo.SelectionDirection,
|
||||
direction tomo.KeynavDirection,
|
||||
) (
|
||||
granted bool,
|
||||
) {
|
||||
if child, ok := window.child.(tomo.Selectable); ok {
|
||||
if !child.HandleSelection(direction) {
|
||||
child.HandleDeselection()
|
||||
if child, ok := window.child.(tomo.Focusable); ok {
|
||||
if !child.HandleFocus(direction) {
|
||||
child.HandleUnfocus()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
57
element.go
57
element.go
@ -28,58 +28,61 @@ type Element interface {
|
||||
OnMinimumSizeChange (callback func ())
|
||||
}
|
||||
|
||||
// SelectionDirection represents a keyboard navigation direction.
|
||||
type SelectionDirection int
|
||||
// KeynavDirection represents a keyboard navigation direction.
|
||||
type KeynavDirection int
|
||||
|
||||
const (
|
||||
SelectionDirectionNeutral SelectionDirection = 0
|
||||
SelectionDirectionBackward SelectionDirection = -1
|
||||
SelectionDirectionForward SelectionDirection = 1
|
||||
KeynavDirectionNeutral KeynavDirection = 0
|
||||
KeynavDirectionBackward KeynavDirection = -1
|
||||
KeynavDirectionForward KeynavDirection = 1
|
||||
)
|
||||
|
||||
// Canon returns a well-formed direction.
|
||||
func (direction SelectionDirection) Canon () (canon SelectionDirection) {
|
||||
func (direction KeynavDirection) Canon () (canon KeynavDirection) {
|
||||
if direction > 0 {
|
||||
return SelectionDirectionForward
|
||||
return KeynavDirectionForward
|
||||
} else if direction == 0 {
|
||||
return SelectionDirectionNeutral
|
||||
return KeynavDirectionNeutral
|
||||
} else {
|
||||
return SelectionDirectionBackward
|
||||
return KeynavDirectionBackward
|
||||
}
|
||||
}
|
||||
|
||||
// Selectable represents an element that has keyboard navigation support. This
|
||||
// Focusable represents an element that has keyboard navigation support. This
|
||||
// includes inputs, buttons, sliders, etc. as well as any elements that have
|
||||
// children (so keyboard navigation events can be propagated downward).
|
||||
type Selectable interface {
|
||||
type Focusable interface {
|
||||
Element
|
||||
|
||||
// Selected returns whether or not this element is currently selected.
|
||||
Selected () (selected bool)
|
||||
// Focused returns whether or not this element is currently focused.
|
||||
Focused () (selected bool)
|
||||
|
||||
// Select selects this element, if its parent element grants the
|
||||
// Focus focuses this element, if its parent element grants the
|
||||
// request.
|
||||
Select ()
|
||||
Focus ()
|
||||
|
||||
// HandleSelection causes this element to mark itself as selected, if it
|
||||
// can currently be. Otherwise, it will return false and do nothing.
|
||||
HandleSelection (direction SelectionDirection) (accepted bool)
|
||||
// HandleFocus causes this element to mark itself as focused. If the
|
||||
// element does not have children, it is disabled, or there are no more
|
||||
// selectable children in the given direction, it should return false
|
||||
// and do nothing. Otherwise, it should select itself and any children
|
||||
// (if applicable) and return true.
|
||||
HandleFocus (direction KeynavDirection) (accepted bool)
|
||||
|
||||
// HandleDeselection causes this element to mark itself and all of its
|
||||
// children as deselected.
|
||||
HandleDeselection ()
|
||||
// children as unfocused.
|
||||
HandleUnfocus ()
|
||||
|
||||
// OnSelectionRequest sets a function to be called when this element
|
||||
// wants its parent element to select it. Parent elements should return
|
||||
// true if the request was granted, and false if it was not.
|
||||
OnSelectionRequest (func () (granted bool))
|
||||
// OnFocusRequest sets a function to be called when this element wants
|
||||
// its parent element to focus it. Parent elements should return true if
|
||||
// the request was granted, and false if it was not.
|
||||
OnFocusRequest (func () (granted bool))
|
||||
|
||||
// OnSelectionMotionRequest sets a function to be called when this
|
||||
// element wants its parent element to select the element behind or in
|
||||
// OnFocusMotionRequest sets a function to be called when this
|
||||
// element wants its parent element to focus the element behind or in
|
||||
// front of it, depending on the specified direction. Parent elements
|
||||
// should return true if the request was granted, and false if it was
|
||||
// not.
|
||||
OnSelectionMotionRequest (func (SelectionDirection) (granted bool))
|
||||
OnFocusMotionRequest (func (direction KeynavDirection) (granted bool))
|
||||
}
|
||||
|
||||
// KeyboardTarget represents an element that can receive keyboard input.
|
||||
|
@ -11,9 +11,9 @@ var buttonCase = theme.C("basic", "button")
|
||||
// Button is a clickable button.
|
||||
type Button struct {
|
||||
*core.Core
|
||||
*core.SelectableCore
|
||||
*core.FocusableCore
|
||||
core core.CoreControl
|
||||
selectableControl core.SelectableCoreControl
|
||||
focusableControl core.FocusableCoreControl
|
||||
drawer artist.TextDrawer
|
||||
|
||||
pressed bool
|
||||
@ -26,8 +26,8 @@ type Button struct {
|
||||
func NewButton (text string) (element *Button) {
|
||||
element = &Button { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.SelectableCore,
|
||||
element.selectableControl = core.NewSelectableCore (func () {
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
@ -45,7 +45,7 @@ func (element *Button) Resize (width, height int) {
|
||||
|
||||
func (element *Button) HandleMouseDown (x, y int, button tomo.Button) {
|
||||
if !element.Enabled() { return }
|
||||
if !element.Selected() { element.Select() }
|
||||
if !element.Focused() { element.Focus() }
|
||||
if button != tomo.ButtonLeft { return }
|
||||
element.pressed = true
|
||||
if element.core.HasImage() {
|
||||
@ -106,7 +106,7 @@ func (element *Button) OnClick (callback func ()) {
|
||||
|
||||
// SetEnabled sets whether this button can be clicked or not.
|
||||
func (element *Button) SetEnabled (enabled bool) {
|
||||
element.selectableControl.SetEnabled(enabled)
|
||||
element.focusableControl.SetEnabled(enabled)
|
||||
}
|
||||
|
||||
// SetText sets the button's label text.
|
||||
@ -131,7 +131,7 @@ func (element *Button) draw () {
|
||||
pattern, inset := theme.ButtonPattern(theme.PatternState {
|
||||
Case: buttonCase,
|
||||
Disabled: !element.Enabled(),
|
||||
Selected: element.Selected(),
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
|
||||
|
@ -11,9 +11,9 @@ var checkboxCase = theme.C("basic", "checkbox")
|
||||
// Checkbox is a toggle-able checkbox with a label.
|
||||
type Checkbox struct {
|
||||
*core.Core
|
||||
*core.SelectableCore
|
||||
*core.FocusableCore
|
||||
core core.CoreControl
|
||||
selectableControl core.SelectableCoreControl
|
||||
focusableControl core.FocusableCoreControl
|
||||
drawer artist.TextDrawer
|
||||
|
||||
pressed bool
|
||||
@ -27,8 +27,8 @@ type Checkbox struct {
|
||||
func NewCheckbox (text string, checked bool) (element *Checkbox) {
|
||||
element = &Checkbox { checked: checked }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.SelectableCore,
|
||||
element.selectableControl = core.NewSelectableCore (func () {
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
@ -47,7 +47,7 @@ func (element *Checkbox) Resize (width, height int) {
|
||||
|
||||
func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) {
|
||||
if !element.Enabled() { return }
|
||||
element.Select()
|
||||
element.Focus()
|
||||
element.pressed = true
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
@ -113,7 +113,7 @@ func (element *Checkbox) Value () (checked bool) {
|
||||
|
||||
// SetEnabled sets whether this checkbox can be toggled or not.
|
||||
func (element *Checkbox) SetEnabled (enabled bool) {
|
||||
element.selectableControl.SetEnabled(enabled)
|
||||
element.focusableControl.SetEnabled(enabled)
|
||||
}
|
||||
|
||||
// SetText sets the checkbox's label text.
|
||||
@ -150,7 +150,7 @@ func (element *Checkbox) draw () {
|
||||
pattern, inset := theme.ButtonPattern(theme.PatternState {
|
||||
Case: checkboxCase,
|
||||
Disabled: !element.Enabled(),
|
||||
Selected: element.Selected(),
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, boxBounds)
|
||||
|
@ -14,16 +14,16 @@ type Container struct {
|
||||
*core.Core
|
||||
core core.CoreControl
|
||||
|
||||
layout tomo.Layout
|
||||
children []tomo.LayoutEntry
|
||||
drags [10]tomo.MouseTarget
|
||||
warping bool
|
||||
selected bool
|
||||
selectable bool
|
||||
flexible bool
|
||||
layout tomo.Layout
|
||||
children []tomo.LayoutEntry
|
||||
drags [10]tomo.MouseTarget
|
||||
warping bool
|
||||
focused bool
|
||||
focusable bool
|
||||
flexible bool
|
||||
|
||||
onSelectionRequest func () (granted bool)
|
||||
onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool)
|
||||
onFocusRequest func () (granted bool)
|
||||
onFocusMotionRequest func (tomo.KeynavDirection) (granted bool)
|
||||
onFlexibleHeightChange func ()
|
||||
}
|
||||
|
||||
@ -57,14 +57,14 @@ func (element *Container) Adopt (child tomo.Element, expand bool) {
|
||||
if child0, ok := child.(tomo.Flexible); ok {
|
||||
child0.OnFlexibleHeightChange(element.updateMinimumSize)
|
||||
}
|
||||
if child0, ok := child.(tomo.Selectable); ok {
|
||||
child0.OnSelectionRequest (func () (granted bool) {
|
||||
return element.childSelectionRequestCallback(child0)
|
||||
if child0, ok := child.(tomo.Focusable); ok {
|
||||
child0.OnFocusRequest (func () (granted bool) {
|
||||
return element.childFocusRequestCallback(child0)
|
||||
})
|
||||
child0.OnSelectionMotionRequest (
|
||||
func (direction tomo.SelectionDirection) (granted bool) {
|
||||
if element.onSelectionMotionRequest == nil { return }
|
||||
return element.onSelectionMotionRequest(direction)
|
||||
child0.OnFocusMotionRequest (
|
||||
func (direction tomo.KeynavDirection) (granted bool) {
|
||||
if element.onFocusMotionRequest == nil { return }
|
||||
return element.onFocusMotionRequest(direction)
|
||||
})
|
||||
}
|
||||
|
||||
@ -132,11 +132,11 @@ func (element *Container) Disown (child tomo.Element) {
|
||||
func (element *Container) clearChildEventHandlers (child tomo.Element) {
|
||||
child.OnDamage(nil)
|
||||
child.OnMinimumSizeChange(nil)
|
||||
if child0, ok := child.(tomo.Selectable); ok {
|
||||
child0.OnSelectionRequest(nil)
|
||||
child0.OnSelectionMotionRequest(nil)
|
||||
if child0.Selected() {
|
||||
child0.HandleDeselection()
|
||||
if child0, ok := child.(tomo.Focusable); ok {
|
||||
child0.OnFocusRequest(nil)
|
||||
child0.OnFocusMotionRequest(nil)
|
||||
if child0.Focused() {
|
||||
child0.HandleUnfocus()
|
||||
}
|
||||
}
|
||||
if child0, ok := child.(tomo.Flexible); ok {
|
||||
@ -238,7 +238,7 @@ func (element *Container) HandleMouseScroll (x, y int, deltaX, deltaY float64) {
|
||||
}
|
||||
|
||||
func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
|
||||
element.forSelected (func (child tomo.Selectable) bool {
|
||||
element.forFocused (func (child tomo.Focusable) bool {
|
||||
child0, handlesKeyboard := child.(tomo.KeyboardTarget)
|
||||
if handlesKeyboard {
|
||||
child0.HandleKeyDown(key, modifiers)
|
||||
@ -248,7 +248,7 @@ func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers)
|
||||
}
|
||||
|
||||
func (element *Container) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) {
|
||||
element.forSelected (func (child tomo.Selectable) bool {
|
||||
element.forFocused (func (child tomo.Focusable) bool {
|
||||
child0, handlesKeyboard := child.(tomo.KeyboardTarget)
|
||||
if handlesKeyboard {
|
||||
child0.HandleKeyUp(key, modifiers)
|
||||
@ -257,103 +257,6 @@ func (element *Container) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) {
|
||||
})
|
||||
}
|
||||
|
||||
func (element *Container) Selected () (selected bool) {
|
||||
return element.selected
|
||||
}
|
||||
|
||||
func (element *Container) Select () {
|
||||
if element.onSelectionRequest != nil {
|
||||
element.onSelectionRequest()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) HandleSelection (direction tomo.SelectionDirection) (ok bool) {
|
||||
if !element.selectable { return false }
|
||||
direction = direction.Canon()
|
||||
|
||||
firstSelected := element.firstSelected()
|
||||
if firstSelected < 0 {
|
||||
// no element is currently selected, so we need to select either
|
||||
// the first or last selectable element depending on the
|
||||
// direction.
|
||||
switch direction {
|
||||
case tomo.SelectionDirectionNeutral, tomo.SelectionDirectionForward:
|
||||
// if we recieve a neutral or forward direction, select
|
||||
// the first selectable element.
|
||||
return element.selectFirstSelectableElement(direction)
|
||||
|
||||
case tomo.SelectionDirectionBackward:
|
||||
// if we recieve a backward direction, select the last
|
||||
// selectable element.
|
||||
return element.selectLastSelectableElement(direction)
|
||||
}
|
||||
} else {
|
||||
// an element is currently selected, so we need to move the
|
||||
// selection in the specified direction
|
||||
firstSelectedChild :=
|
||||
element.children[firstSelected].Element.(tomo.Selectable)
|
||||
|
||||
// before we move the selection, the currently selected child
|
||||
// may also be able to move its selection. if the child is able
|
||||
// to do that, we will let it and not move ours.
|
||||
if firstSelectedChild.HandleSelection(direction) {
|
||||
return true
|
||||
}
|
||||
|
||||
// find the previous/next selectable element relative to the
|
||||
// currently selected element, if it exists.
|
||||
for index := firstSelected + int(direction);
|
||||
index < len(element.children) && index >= 0;
|
||||
index += int(direction) {
|
||||
|
||||
child, selectable :=
|
||||
element.children[index].
|
||||
Element.(tomo.Selectable)
|
||||
if selectable && child.HandleSelection(direction) {
|
||||
// we have found one, so we now actually move
|
||||
// the selection.
|
||||
firstSelectedChild.HandleDeselection()
|
||||
element.selected = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (element *Container) selectFirstSelectableElement (
|
||||
direction tomo.SelectionDirection,
|
||||
) (
|
||||
ok bool,
|
||||
) {
|
||||
element.forSelectable (func (child tomo.Selectable) bool {
|
||||
if child.HandleSelection(direction) {
|
||||
element.selected = true
|
||||
ok = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Container) selectLastSelectableElement (
|
||||
direction tomo.SelectionDirection,
|
||||
) (
|
||||
ok bool,
|
||||
) {
|
||||
element.forSelectableBackward (func (child tomo.Selectable) bool {
|
||||
if child.HandleSelection(direction) {
|
||||
element.selected = true
|
||||
ok = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Container) FlexibleHeightFor (width int) (height int) {
|
||||
return element.layout.FlexibleHeightFor(element.children, width)
|
||||
}
|
||||
@ -362,37 +265,134 @@ func (element *Container) OnFlexibleHeightChange (callback func ()) {
|
||||
element.onFlexibleHeightChange = callback
|
||||
}
|
||||
|
||||
func (element *Container) HandleDeselection () {
|
||||
element.selected = false
|
||||
element.forSelected (func (child tomo.Selectable) bool {
|
||||
child.HandleDeselection()
|
||||
func (element *Container) Focused () (focused bool) {
|
||||
return element.focused
|
||||
}
|
||||
|
||||
func (element *Container) Focus () {
|
||||
if element.onFocusRequest != nil {
|
||||
element.onFocusRequest()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) HandleFocus (direction tomo.KeynavDirection) (ok bool) {
|
||||
if !element.focusable { return false }
|
||||
direction = direction.Canon()
|
||||
|
||||
firstFocused := element.firstFocused()
|
||||
if firstFocused < 0 {
|
||||
// no element is currently focused, so we need to focus either
|
||||
// the first or last focusable element depending on the
|
||||
// direction.
|
||||
switch direction {
|
||||
case tomo.KeynavDirectionNeutral, tomo.KeynavDirectionForward:
|
||||
// if we recieve a neutral or forward direction, focus
|
||||
// the first focusable element.
|
||||
return element.focusFirstFocusableElement(direction)
|
||||
|
||||
case tomo.KeynavDirectionBackward:
|
||||
// if we recieve a backward direction, focus the last
|
||||
// focusable element.
|
||||
return element.focusLastFocusableElement(direction)
|
||||
}
|
||||
} else {
|
||||
// an element is currently focused, so we need to move the
|
||||
// focus in the specified direction
|
||||
firstFocusedChild :=
|
||||
element.children[firstFocused].Element.(tomo.Focusable)
|
||||
|
||||
// before we move the focus, the currently focused child
|
||||
// may also be able to move its focus. if the child is able
|
||||
// to do that, we will let it and not move ours.
|
||||
if firstFocusedChild.HandleFocus(direction) {
|
||||
return true
|
||||
}
|
||||
|
||||
// find the previous/next focusable element relative to the
|
||||
// currently focused element, if it exists.
|
||||
for index := firstFocused + int(direction);
|
||||
index < len(element.children) && index >= 0;
|
||||
index += int(direction) {
|
||||
|
||||
child, focusable :=
|
||||
element.children[index].
|
||||
Element.(tomo.Focusable)
|
||||
if focusable && child.HandleFocus(direction) {
|
||||
// we have found one, so we now actually move
|
||||
// the focus.
|
||||
firstFocusedChild.HandleUnfocus()
|
||||
element.focused = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (element *Container) focusFirstFocusableElement (
|
||||
direction tomo.KeynavDirection,
|
||||
) (
|
||||
ok bool,
|
||||
) {
|
||||
element.forFocusable (func (child tomo.Focusable) bool {
|
||||
if child.HandleFocus(direction) {
|
||||
element.focused = true
|
||||
ok = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Container) focusLastFocusableElement (
|
||||
direction tomo.KeynavDirection,
|
||||
) (
|
||||
ok bool,
|
||||
) {
|
||||
element.forFocusableBackward (func (child tomo.Focusable) bool {
|
||||
if child.HandleFocus(direction) {
|
||||
element.focused = true
|
||||
ok = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Container) HandleUnfocus () {
|
||||
element.focused = false
|
||||
element.forFocused (func (child tomo.Focusable) bool {
|
||||
child.HandleUnfocus()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (element *Container) OnSelectionRequest (callback func () (granted bool)) {
|
||||
element.onSelectionRequest = callback
|
||||
func (element *Container) OnFocusRequest (callback func () (granted bool)) {
|
||||
element.onFocusRequest = callback
|
||||
}
|
||||
|
||||
func (element *Container) OnSelectionMotionRequest (
|
||||
callback func (direction tomo.SelectionDirection) (granted bool),
|
||||
func (element *Container) OnFocusMotionRequest (
|
||||
callback func (direction tomo.KeynavDirection) (granted bool),
|
||||
) {
|
||||
element.onSelectionMotionRequest = callback
|
||||
element.onFocusMotionRequest = callback
|
||||
}
|
||||
|
||||
func (element *Container) forSelected (callback func (child tomo.Selectable) bool) {
|
||||
func (element *Container) forFocused (callback func (child tomo.Focusable) bool) {
|
||||
for _, entry := range element.children {
|
||||
child, selectable := entry.Element.(tomo.Selectable)
|
||||
if selectable && child.Selected() {
|
||||
child, focusable := entry.Element.(tomo.Focusable)
|
||||
if focusable && child.Focused() {
|
||||
if !callback(child) { break }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) forSelectable (callback func (child tomo.Selectable) bool) {
|
||||
func (element *Container) forFocusable (callback func (child tomo.Focusable) bool) {
|
||||
for _, entry := range element.children {
|
||||
child, selectable := entry.Element.(tomo.Selectable)
|
||||
if selectable {
|
||||
child, focusable := entry.Element.(tomo.Focusable)
|
||||
if focusable {
|
||||
if !callback(child) { break }
|
||||
}
|
||||
}
|
||||
@ -400,26 +400,26 @@ func (element *Container) forSelectable (callback func (child tomo.Selectable) b
|
||||
|
||||
func (element *Container) forFlexible (callback func (child tomo.Flexible) bool) {
|
||||
for _, entry := range element.children {
|
||||
child, selectable := entry.Element.(tomo.Flexible)
|
||||
if selectable {
|
||||
child, flexible := entry.Element.(tomo.Flexible)
|
||||
if flexible {
|
||||
if !callback(child) { break }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) forSelectableBackward (callback func (child tomo.Selectable) bool) {
|
||||
func (element *Container) forFocusableBackward (callback func (child tomo.Focusable) bool) {
|
||||
for index := len(element.children) - 1; index >= 0; index -- {
|
||||
child, selectable := element.children[index].Element.(tomo.Selectable)
|
||||
if selectable {
|
||||
child, focusable := element.children[index].Element.(tomo.Focusable)
|
||||
if focusable {
|
||||
if !callback(child) { break }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) firstSelected () (index int) {
|
||||
func (element *Container) firstFocused () (index int) {
|
||||
for currentIndex, entry := range element.children {
|
||||
child, selectable := entry.Element.(tomo.Selectable)
|
||||
if selectable && child.Selected() {
|
||||
child, focusable := entry.Element.(tomo.Focusable)
|
||||
if focusable && child.Focused() {
|
||||
return currentIndex
|
||||
}
|
||||
}
|
||||
@ -427,9 +427,9 @@ func (element *Container) firstSelected () (index int) {
|
||||
}
|
||||
|
||||
func (element *Container) reflectChildProperties () {
|
||||
element.selectable = false
|
||||
element.forSelectable (func (tomo.Selectable) bool {
|
||||
element.selectable = true
|
||||
element.focusable = false
|
||||
element.forFocusable (func (tomo.Focusable) bool {
|
||||
element.focusable = true
|
||||
return false
|
||||
})
|
||||
element.flexible = false
|
||||
@ -437,22 +437,22 @@ func (element *Container) reflectChildProperties () {
|
||||
element.flexible = true
|
||||
return false
|
||||
})
|
||||
if !element.selectable {
|
||||
element.selected = false
|
||||
if !element.focusable {
|
||||
element.focused = false
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) childSelectionRequestCallback (
|
||||
child tomo.Selectable,
|
||||
func (element *Container) childFocusRequestCallback (
|
||||
child tomo.Focusable,
|
||||
) (
|
||||
granted bool,
|
||||
) {
|
||||
if element.onSelectionRequest != nil && element.onSelectionRequest() {
|
||||
element.forSelected (func (child tomo.Selectable) bool {
|
||||
child.HandleDeselection()
|
||||
if element.onFocusRequest != nil && element.onFocusRequest() {
|
||||
element.forFocused (func (child tomo.Focusable) bool {
|
||||
child.HandleUnfocus()
|
||||
return true
|
||||
})
|
||||
child.HandleSelection(tomo.SelectionDirectionNeutral)
|
||||
child.HandleFocus(tomo.KeynavDirectionNeutral)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
@ -12,9 +12,9 @@ var listCase = theme.C("basic", "list")
|
||||
// List is an element that contains several objects that a user can select.
|
||||
type List struct {
|
||||
*core.Core
|
||||
*core.SelectableCore
|
||||
*core.FocusableCore
|
||||
core core.CoreControl
|
||||
selectableControl core.SelectableCoreControl
|
||||
focusableControl core.FocusableCoreControl
|
||||
|
||||
pressed bool
|
||||
|
||||
@ -34,8 +34,8 @@ type List struct {
|
||||
func NewList (entries ...ListEntry) (element *List) {
|
||||
element = &List { selectedEntry: -1 }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.SelectableCore,
|
||||
element.selectableControl = core.NewSelectableCore (func () {
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
@ -78,7 +78,7 @@ func (element *List) Collapse (width, height int) {
|
||||
|
||||
func (element *List) HandleMouseDown (x, y int, button tomo.Button) {
|
||||
if !element.Enabled() { return }
|
||||
if !element.Selected() { element.Select() }
|
||||
if !element.Focused() { element.Focus() }
|
||||
if button != tomo.ButtonLeft { return }
|
||||
element.pressed = true
|
||||
if element.selectUnderMouse(x, y) && element.core.HasImage() {
|
||||
@ -377,7 +377,7 @@ func (element *List) draw () {
|
||||
pattern, inset := theme.ListPattern(theme.PatternState {
|
||||
Case: listCase,
|
||||
Disabled: !element.Enabled(),
|
||||
Selected: element.Selected(),
|
||||
Focused: element.Focused(),
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
|
||||
@ -394,6 +394,6 @@ func (element *List) draw () {
|
||||
if entryPosition.Y > bounds.Max.Y { break }
|
||||
entry.Draw (
|
||||
innerCanvas, entryPosition,
|
||||
element.Selected(), element.selectedEntry == index)
|
||||
element.Focused(), element.selectedEntry == index)
|
||||
}
|
||||
}
|
||||
|
@ -55,14 +55,14 @@ func (entry *ListEntry) updateBounds () {
|
||||
func (entry *ListEntry) Draw (
|
||||
destination tomo.Canvas,
|
||||
offset image.Point,
|
||||
selected bool,
|
||||
focused bool,
|
||||
on bool,
|
||||
) (
|
||||
updatedRegion image.Rectangle,
|
||||
) {
|
||||
pattern, _ := theme.ItemPattern(theme.PatternState {
|
||||
Case: listEntryCase,
|
||||
Selected: selected,
|
||||
Focused: focused,
|
||||
On: on,
|
||||
})
|
||||
artist.FillRectangle (
|
||||
@ -71,7 +71,7 @@ func (entry *ListEntry) Draw (
|
||||
entry.Bounds().Add(offset))
|
||||
foreground, _ := theme.ForegroundPattern (theme.PatternState {
|
||||
Case: listEntryCase,
|
||||
Selected: selected,
|
||||
Focused: focused,
|
||||
On: on,
|
||||
})
|
||||
return entry.drawer.Draw (
|
||||
|
@ -15,7 +15,7 @@ var scrollBarVerticalCase = theme.C("basic", "scrollBarVertical")
|
||||
type ScrollContainer struct {
|
||||
*core.Core
|
||||
core core.CoreControl
|
||||
selected bool
|
||||
focused bool
|
||||
|
||||
child tomo.Scrollable
|
||||
childWidth, childHeight int
|
||||
@ -40,8 +40,8 @@ type ScrollContainer struct {
|
||||
bar image.Rectangle
|
||||
}
|
||||
|
||||
onSelectionRequest func () (granted bool)
|
||||
onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool)
|
||||
onFocusRequest func () (granted bool)
|
||||
onFocusMotionRequest func (tomo.KeynavDirection) (granted bool)
|
||||
}
|
||||
|
||||
// NewScrollContainer creates a new scroll container with the specified scroll
|
||||
@ -80,11 +80,11 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
|
||||
child.OnDamage(element.childDamageCallback)
|
||||
child.OnMinimumSizeChange(element.updateMinimumSize)
|
||||
child.OnScrollBoundsChange(element.childScrollBoundsChangeCallback)
|
||||
if newChild, ok := child.(tomo.Selectable); ok {
|
||||
newChild.OnSelectionRequest (
|
||||
element.childSelectionRequestCallback)
|
||||
newChild.OnSelectionMotionRequest (
|
||||
element.childSelectionMotionRequestCallback)
|
||||
if newChild, ok := child.(tomo.Focusable); ok {
|
||||
newChild.OnFocusRequest (
|
||||
element.childFocusRequestCallback)
|
||||
newChild.OnFocusMotionRequest (
|
||||
element.childFocusMotionRequestCallback)
|
||||
}
|
||||
|
||||
// TODO: somehow inform the core that we do not in fact want to
|
||||
@ -193,82 +193,80 @@ func (element *ScrollContainer) scrollChildBy (x, y int) {
|
||||
element.child.ScrollTo(scrollPoint)
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) Selected () (selected bool) {
|
||||
return element.selected
|
||||
func (element *ScrollContainer) Focused () (focused bool) {
|
||||
return element.focused
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) Select () {
|
||||
if element.onSelectionRequest != nil {
|
||||
element.onSelectionRequest()
|
||||
func (element *ScrollContainer) Focus () {
|
||||
if element.onFocusRequest != nil {
|
||||
element.onFocusRequest()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) HandleSelection (
|
||||
direction tomo.SelectionDirection,
|
||||
func (element *ScrollContainer) HandleFocus (
|
||||
direction tomo.KeynavDirection,
|
||||
) (
|
||||
accepted bool,
|
||||
) {
|
||||
if child, ok := element.child.(tomo.Selectable); ok {
|
||||
element.selected = true
|
||||
return child.HandleSelection(direction)
|
||||
if child, ok := element.child.(tomo.Focusable); ok {
|
||||
element.focused = true
|
||||
return child.HandleFocus(direction)
|
||||
} else {
|
||||
element.selected = false
|
||||
element.focused = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) HandleDeselection () {
|
||||
if child, ok := element.child.(tomo.Selectable); ok {
|
||||
child.HandleDeselection()
|
||||
func (element *ScrollContainer) HandleUnfocus () {
|
||||
if child, ok := element.child.(tomo.Focusable); ok {
|
||||
child.HandleUnfocus()
|
||||
}
|
||||
element.selected = false
|
||||
element.focused = false
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) OnSelectionRequest (callback func () (granted bool)) {
|
||||
element.onSelectionRequest = callback
|
||||
func (element *ScrollContainer) OnFocusRequest (callback func () (granted bool)) {
|
||||
element.onFocusRequest = callback
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) OnSelectionMotionRequest (
|
||||
callback func (direction tomo.SelectionDirection) (granted bool),
|
||||
func (element *ScrollContainer) OnFocusMotionRequest (
|
||||
callback func (direction tomo.KeynavDirection) (granted bool),
|
||||
) {
|
||||
element.onSelectionMotionRequest = callback
|
||||
element.onFocusMotionRequest = callback
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) childDamageCallback (region tomo.Canvas) {
|
||||
element.core.DamageRegion(artist.Paste(element, region, image.Point { }))
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) childSelectionRequestCallback () (granted bool) {
|
||||
child, ok := element.child.(tomo.Selectable)
|
||||
func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
|
||||
child, ok := element.child.(tomo.Focusable)
|
||||
if !ok { return false }
|
||||
if element.onSelectionRequest != nil && element.onSelectionRequest() {
|
||||
child.HandleSelection(tomo.SelectionDirectionNeutral)
|
||||
if element.onFocusRequest != nil && element.onFocusRequest() {
|
||||
child.HandleFocus(tomo.KeynavDirectionNeutral)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) childSelectionMotionRequestCallback (
|
||||
direction tomo.SelectionDirection,
|
||||
func (element *ScrollContainer) childFocusMotionRequestCallback (
|
||||
direction tomo.KeynavDirection,
|
||||
) (
|
||||
granted bool,
|
||||
) {
|
||||
if element.onSelectionMotionRequest == nil {
|
||||
return
|
||||
}
|
||||
return element.onSelectionMotionRequest(direction)
|
||||
if element.onFocusMotionRequest == nil { return }
|
||||
return element.onFocusMotionRequest(direction)
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable) {
|
||||
child.OnDamage(nil)
|
||||
child.OnMinimumSizeChange(nil)
|
||||
child.OnScrollBoundsChange(nil)
|
||||
if child0, ok := child.(tomo.Selectable); ok {
|
||||
child0.OnSelectionRequest(nil)
|
||||
child0.OnSelectionMotionRequest(nil)
|
||||
if child0.Selected() {
|
||||
child0.HandleDeselection()
|
||||
if child0, ok := child.(tomo.Focusable); ok {
|
||||
child0.OnFocusRequest(nil)
|
||||
child0.OnFocusMotionRequest(nil)
|
||||
if child0.Focused() {
|
||||
child0.HandleUnfocus()
|
||||
}
|
||||
}
|
||||
if child0, ok := child.(tomo.Flexible); ok {
|
||||
|
@ -12,9 +12,9 @@ var switchCase = theme.C("basic", "switch")
|
||||
// functionally identical to Checkbox, but plays a different semantic role.
|
||||
type Switch struct {
|
||||
*core.Core
|
||||
*core.SelectableCore
|
||||
*core.FocusableCore
|
||||
core core.CoreControl
|
||||
selectableControl core.SelectableCoreControl
|
||||
focusableControl core.FocusableCoreControl
|
||||
drawer artist.TextDrawer
|
||||
|
||||
pressed bool
|
||||
@ -28,8 +28,8 @@ type Switch struct {
|
||||
func NewSwitch (text string, on bool) (element *Switch) {
|
||||
element = &Switch { checked: on, text: text }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.SelectableCore,
|
||||
element.selectableControl = core.NewSelectableCore (func () {
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
@ -49,7 +49,7 @@ func (element *Switch) Resize (width, height int) {
|
||||
|
||||
func (element *Switch) HandleMouseDown (x, y int, button tomo.Button) {
|
||||
if !element.Enabled() { return }
|
||||
element.Select()
|
||||
element.Focus()
|
||||
element.pressed = true
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
@ -115,7 +115,7 @@ func (element *Switch) Value () (on bool) {
|
||||
|
||||
// SetEnabled sets whether this switch can be flipped or not.
|
||||
func (element *Switch) SetEnabled (enabled bool) {
|
||||
element.selectableControl.SetEnabled(enabled)
|
||||
element.focusableControl.SetEnabled(enabled)
|
||||
}
|
||||
|
||||
// SetText sets the checkbox's label text.
|
||||
@ -171,7 +171,7 @@ func (element *Switch) draw () {
|
||||
gutterPattern, _ := theme.GutterPattern(theme.PatternState {
|
||||
Case: switchCase,
|
||||
Disabled: !element.Enabled(),
|
||||
Selected: element.Selected(),
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
artist.FillRectangle(element.core, gutterPattern, gutterBounds)
|
||||
@ -179,7 +179,7 @@ func (element *Switch) draw () {
|
||||
handlePattern, _ := theme.HandlePattern(theme.PatternState {
|
||||
Case: switchCase,
|
||||
Disabled: !element.Enabled(),
|
||||
Selected: element.Selected(),
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
artist.FillRectangle(element.core, handlePattern, handleBounds)
|
||||
|
@ -12,9 +12,9 @@ var textBoxCase = theme.C("basic", "textBox")
|
||||
// TextBox is a single-line text input.
|
||||
type TextBox struct {
|
||||
*core.Core
|
||||
*core.SelectableCore
|
||||
*core.FocusableCore
|
||||
core core.CoreControl
|
||||
selectableControl core.SelectableCoreControl
|
||||
focusableControl core.FocusableCoreControl
|
||||
|
||||
cursor int
|
||||
scroll int
|
||||
@ -35,8 +35,8 @@ type TextBox struct {
|
||||
func NewTextBox (placeholder, value string) (element *TextBox) {
|
||||
element = &TextBox { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.SelectableCore,
|
||||
element.selectableControl = core.NewSelectableCore (func () {
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
@ -61,8 +61,8 @@ func (element *TextBox) Resize (width, height int) {
|
||||
}
|
||||
|
||||
func (element *TextBox) HandleMouseDown (x, y int, button tomo.Button) {
|
||||
if !element.Enabled() { return }
|
||||
if !element.Selected() { element.Select() }
|
||||
if !element.Enabled() { return }
|
||||
if !element.Focused() { element.Focus() }
|
||||
}
|
||||
|
||||
func (element *TextBox) HandleMouseUp (x, y int, button tomo.Button) { }
|
||||
@ -277,13 +277,13 @@ func (element *TextBox) draw () {
|
||||
|
||||
// FIXME: take index into account
|
||||
pattern, inset := theme.InputPattern(theme.PatternState {
|
||||
Case: textBoxCase,
|
||||
Case: textBoxCase,
|
||||
Disabled: !element.Enabled(),
|
||||
Selected: element.Selected(),
|
||||
Focused: element.Focused(),
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
|
||||
if len(element.text) == 0 && !element.Selected() {
|
||||
if len(element.text) == 0 && !element.Focused() {
|
||||
// draw placeholder
|
||||
textBounds := element.placeholderDrawer.LayoutBounds()
|
||||
offset := image.Point {
|
||||
@ -314,7 +314,7 @@ func (element *TextBox) draw () {
|
||||
foreground,
|
||||
offset.Sub(textBounds.Min))
|
||||
|
||||
if element.Selected() {
|
||||
if element.Focused() {
|
||||
// cursor
|
||||
cursorPosition := element.valueDrawer.PositionOf (
|
||||
element.cursor)
|
||||
|
@ -15,9 +15,6 @@ type Core struct {
|
||||
minimumHeight int
|
||||
}
|
||||
|
||||
selectable bool
|
||||
selected bool
|
||||
|
||||
onMinimumSizeChange func ()
|
||||
onDamage func (region tomo.Canvas)
|
||||
}
|
||||
|
@ -2,110 +2,110 @@ package core
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
|
||||
// SelectableCore is a struct that can be embedded into objects to make them
|
||||
// selectable, giving them the default selectability behavior.
|
||||
type SelectableCore struct {
|
||||
selected bool
|
||||
enabled bool
|
||||
drawSelectionChange func ()
|
||||
onSelectionRequest func () (granted bool)
|
||||
onSelectionMotionRequest func(tomo.SelectionDirection) (granted bool)
|
||||
// FocusableCore is a struct that can be embedded into objects to make them
|
||||
// focusable, giving them the default keynav behavior.
|
||||
type FocusableCore struct {
|
||||
focused bool
|
||||
enabled bool
|
||||
drawFocusChange func ()
|
||||
onFocusRequest func () (granted bool)
|
||||
onFocusMotionRequest func(tomo.KeynavDirection) (granted bool)
|
||||
}
|
||||
|
||||
// NewSelectableCore creates a new selectability core and its corresponding
|
||||
// control. If your element needs to visually update itself when it's selection
|
||||
// NewFocusableCore creates a new focusability core and its corresponding
|
||||
// control. If your element needs to visually update itself when it's focus
|
||||
// state changes (which it should), a callback to draw and push the update can
|
||||
// be specified.
|
||||
func NewSelectableCore (
|
||||
drawSelectionChange func (),
|
||||
func NewFocusableCore (
|
||||
drawFocusChange func (),
|
||||
) (
|
||||
core *SelectableCore,
|
||||
control SelectableCoreControl,
|
||||
core *FocusableCore,
|
||||
control FocusableCoreControl,
|
||||
) {
|
||||
core = &SelectableCore {
|
||||
drawSelectionChange: drawSelectionChange,
|
||||
core = &FocusableCore {
|
||||
drawFocusChange: drawFocusChange,
|
||||
enabled: true,
|
||||
}
|
||||
control = SelectableCoreControl { core: core }
|
||||
control = FocusableCoreControl { core: core }
|
||||
return
|
||||
}
|
||||
|
||||
// Selected returns whether or not this element is currently selected.
|
||||
func (core *SelectableCore) Selected () (selected bool) {
|
||||
return core.selected
|
||||
// Focused returns whether or not this element is currently focused.
|
||||
func (core *FocusableCore) Focused () (focused bool) {
|
||||
return core.focused
|
||||
}
|
||||
|
||||
// Select selects this element, if its parent element grants the request.
|
||||
func (core *SelectableCore) Select () {
|
||||
// Focus focuses this element, if its parent element grants the request.
|
||||
func (core *FocusableCore) Focus () {
|
||||
if !core.enabled { return }
|
||||
if core.onSelectionRequest != nil {
|
||||
core.onSelectionRequest()
|
||||
if core.onFocusRequest != nil {
|
||||
core.onFocusRequest()
|
||||
}
|
||||
}
|
||||
|
||||
// HandleSelection causes this element to mark itself as selected, if it can
|
||||
// HandleFocus causes this element to mark itself as focused, if it can
|
||||
// currently be. Otherwise, it will return false and do nothing.
|
||||
func (core *SelectableCore) HandleSelection (
|
||||
direction tomo.SelectionDirection,
|
||||
func (core *FocusableCore) HandleFocus (
|
||||
direction tomo.KeynavDirection,
|
||||
) (
|
||||
accepted bool,
|
||||
) {
|
||||
direction = direction.Canon()
|
||||
if !core.enabled { return false }
|
||||
if core.selected && direction != tomo.SelectionDirectionNeutral {
|
||||
if core.focused && direction != tomo.KeynavDirectionNeutral {
|
||||
return false
|
||||
}
|
||||
|
||||
core.selected = true
|
||||
if core.drawSelectionChange != nil { core.drawSelectionChange() }
|
||||
core.focused = true
|
||||
if core.drawFocusChange != nil { core.drawFocusChange() }
|
||||
return true
|
||||
}
|
||||
|
||||
// HandleDeselection causes this element to mark itself as deselected.
|
||||
func (core *SelectableCore) HandleDeselection () {
|
||||
core.selected = false
|
||||
if core.drawSelectionChange != nil { core.drawSelectionChange() }
|
||||
// HandleUnfocus causes this element to mark itself as unfocused.
|
||||
func (core *FocusableCore) HandleUnfocus () {
|
||||
core.focused = false
|
||||
if core.drawFocusChange != nil { core.drawFocusChange() }
|
||||
}
|
||||
|
||||
// OnSelectionRequest sets a function to be called when this element
|
||||
// wants its parent element to select it. Parent elements should return
|
||||
// OnFocusRequest sets a function to be called when this element
|
||||
// wants its parent element to focus it. Parent elements should return
|
||||
// true if the request was granted, and false if it was not.
|
||||
func (core *SelectableCore) OnSelectionRequest (callback func () (granted bool)) {
|
||||
core.onSelectionRequest = callback
|
||||
func (core *FocusableCore) OnFocusRequest (callback func () (granted bool)) {
|
||||
core.onFocusRequest = callback
|
||||
}
|
||||
|
||||
// OnSelectionMotionRequest sets a function to be called when this
|
||||
// element wants its parent element to select the element behind or in
|
||||
// OnFocusMotionRequest sets a function to be called when this
|
||||
// element wants its parent element to focus the element behind or in
|
||||
// front of it, depending on the specified direction. Parent elements
|
||||
// should return true if the request was granted, and false if it was
|
||||
// not.
|
||||
func (core *SelectableCore) OnSelectionMotionRequest (
|
||||
callback func (direction tomo.SelectionDirection) (granted bool),
|
||||
func (core *FocusableCore) OnFocusMotionRequest (
|
||||
callback func (direction tomo.KeynavDirection) (granted bool),
|
||||
) {
|
||||
core.onSelectionMotionRequest = callback
|
||||
core.onFocusMotionRequest = callback
|
||||
}
|
||||
|
||||
// Enabled returns whether or not the element is enabled.
|
||||
func (core *SelectableCore) Enabled () (enabled bool) {
|
||||
func (core *FocusableCore) Enabled () (enabled bool) {
|
||||
return core.enabled
|
||||
}
|
||||
|
||||
// SelectableCoreControl is a struct that can be used to exert control over a
|
||||
// selectability core. It must not be directly embedded into an element, but
|
||||
// instead kept as a private member. When a SelectableCore struct is created, a
|
||||
// corresponding SelectableCoreControl struct is linked to it and returned
|
||||
// FocusableCoreControl is a struct that can be used to exert control over a
|
||||
// focusability core. It must not be directly embedded into an element, but
|
||||
// instead kept as a private member. When a FocusableCore struct is created, a
|
||||
// corresponding FocusableCoreControl struct is linked to it and returned
|
||||
// alongside it.
|
||||
type SelectableCoreControl struct {
|
||||
core *SelectableCore
|
||||
type FocusableCoreControl struct {
|
||||
core *FocusableCore
|
||||
}
|
||||
|
||||
// SetEnabled sets whether the selectability core is enabled. If the state
|
||||
// changes, this will call drawSelectionChange.
|
||||
func (control SelectableCoreControl) SetEnabled (enabled bool) {
|
||||
// SetEnabled sets whether the focusability core is enabled. If the state
|
||||
// changes, this will call drawFocusChange.
|
||||
func (control FocusableCoreControl) SetEnabled (enabled bool) {
|
||||
if control.core.enabled == enabled { return }
|
||||
control.core.enabled = enabled
|
||||
if !enabled { control.core.selected = false }
|
||||
if control.core.drawSelectionChange != nil {
|
||||
control.core.drawSelectionChange()
|
||||
if !enabled { control.core.focused = false }
|
||||
if control.core.drawFocusChange != nil {
|
||||
control.core.drawFocusChange()
|
||||
}
|
||||
}
|
||||
|
@ -26,3 +26,9 @@ var listEntryPattern = artist.NewMultiBordered (
|
||||
|
||||
var onListEntryPattern = artist.NewMultiBordered (
|
||||
artist.Stroke { Pattern: artist.NewUniform(hex(0x6e8079FF)) })
|
||||
|
||||
var selectedListEntryPattern = artist.NewMultiBordered (
|
||||
artist.Stroke { Pattern: artist.NewUniform(hex(0x999C99FF)) })
|
||||
|
||||
var selectedOnListEntryPattern = artist.NewMultiBordered (
|
||||
artist.Stroke { Pattern: artist.NewUniform(hex(0x6e8079FF)) })
|
||||
|
@ -31,9 +31,9 @@ type PatternState struct {
|
||||
// question is capable of being toggled.
|
||||
On bool
|
||||
|
||||
// Selected should be set to true if the element that is using this
|
||||
// pattern is currently selected.
|
||||
Selected bool
|
||||
// Focused should be set to true if the element that is using this
|
||||
// pattern is currently focused.
|
||||
Focused bool
|
||||
|
||||
// Pressed should be set to true if the element that is using this
|
||||
// pattern is being pressed down by the mouse. This is only necessary if
|
||||
@ -123,7 +123,7 @@ func InputPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
if state.Disabled {
|
||||
return disabledInputPattern, Inset { 1, 1, 1, 1 }
|
||||
} else {
|
||||
if state.Selected {
|
||||
if state.Focused {
|
||||
return selectedInputPattern, Inset { 1, 1, 1, 1 }
|
||||
} else {
|
||||
return inputPattern, Inset { 1, 1, 1, 1 }
|
||||
@ -133,7 +133,7 @@ func InputPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
|
||||
// ListPattern returns a background pattern for a list of things.
|
||||
func ListPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
if state.Selected {
|
||||
if state.Focused {
|
||||
return selectedListPattern, Inset { 4, 4, 4, 4 }
|
||||
} else {
|
||||
return listPattern, Inset { 4, 4, 4, 4 }
|
||||
@ -142,10 +142,18 @@ func ListPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
|
||||
// ItemPattern returns a background pattern for a list item.
|
||||
func ItemPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
if state.On {
|
||||
return onListEntryPattern, Inset { 4, 4, 4, 4 }
|
||||
if state.Focused {
|
||||
if state.On {
|
||||
return selectedOnListEntryPattern, Inset { 4, 4, 4, 4 }
|
||||
} else {
|
||||
return selectedListEntryPattern, Inset { 4, 4, 4, 4 }
|
||||
}
|
||||
} else {
|
||||
return listEntryPattern, Inset { 4, 4, 4, 4 }
|
||||
if state.On {
|
||||
return onListEntryPattern, Inset { 4, 4, 4, 4 }
|
||||
} else {
|
||||
return listEntryPattern, Inset { 4, 4, 4, 4 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,14 +163,14 @@ func ButtonPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
return disabledButtonPattern, Inset { 1, 1, 1, 1 }
|
||||
} else {
|
||||
if state.Pressed {
|
||||
if state.Selected {
|
||||
if state.Focused {
|
||||
return pressedSelectedButtonPattern, Inset {
|
||||
2, 0, 0, 2 }
|
||||
} else {
|
||||
return pressedButtonPattern, Inset { 2, 0, 0, 2 }
|
||||
}
|
||||
} else {
|
||||
if state.Selected {
|
||||
if state.Focused {
|
||||
return selectedButtonPattern, Inset { 1, 1, 1, 1 }
|
||||
} else {
|
||||
return buttonPattern, Inset { 1, 1, 1, 1 }
|
||||
@ -187,7 +195,7 @@ func HandlePattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
if state.Disabled {
|
||||
return disabledScrollBarPattern, Inset { 1, 1, 1, 1 }
|
||||
} else {
|
||||
if state.Selected {
|
||||
if state.Focused {
|
||||
if state.Pressed {
|
||||
return pressedSelectedScrollBarPattern, Inset { 1, 1, 1, 1 }
|
||||
} else {
|
||||
@ -212,7 +220,7 @@ func SunkenPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
// RaisedPattern returns a general purpose pattern that is raised up out of the
|
||||
// background.
|
||||
func RaisedPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||
if state.Selected {
|
||||
if state.Focused {
|
||||
return selectedRaisedPattern, Inset { 1, 1, 1, 1 }
|
||||
} else {
|
||||
return raisedPattern, Inset { 1, 1, 1, 1 }
|
||||
|
Reference in New Issue
Block a user