io/input: implement lazy event routing

This change defers event routing from the time the event is queued until
the time Events is called. This allows a future change to execute
commands immediately and to react to event order changes during a frame.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-11-15 19:14:14 -06:00
parent 651094d692
commit 9dfada745c
8 changed files with 390 additions and 230 deletions
+62 -56
View File
@@ -24,13 +24,17 @@ type EditorState struct {
type TextInputState uint8
type keyQueue struct {
focus event.Tag
order []event.Tag
dirOrder []dirFocusEntry
handlers map[event.Tag]*keyHandler
state TextInputState
hint key.InputHint
content EditorState
}
// keyState is the input state related to key events.
type keyState struct {
focus event.Tag
state TextInputState
content EditorState
}
type keyHandler struct {
@@ -67,21 +71,18 @@ func (q *keyQueue) inputHint(op key.InputHintOp) {
h.hint = op.Hint
}
// InputState returns the last text input state as
// determined in Frame.
func (q *keyQueue) InputState() TextInputState {
state := q.state
q.state = TextInputKeep
return state
// InputState returns the input state and returns a state
// reset to [TextInputKeep].
func (s keyState) InputState() (keyState, TextInputState) {
state := s.state
s.state = TextInputKeep
return s, state
}
// InputHint returns the input hint from the focused handler and whether it was
// changed since the last call.
func (q *keyQueue) InputHint() (key.InputHint, bool) {
if q.focus == nil {
return q.hint, false
}
focused, ok := q.handlers[q.focus]
func (q *keyQueue) InputHint(state keyState) (key.InputHint, bool) {
focused, ok := q.handlers[state.focus]
if !ok {
return q.hint, false
}
@@ -108,13 +109,13 @@ func (q *keyQueue) ResetEvent(k event.Tag) (event.Event, bool) {
return key.FocusEvent{Focus: false}, true
}
func (q *keyQueue) Frame() {
func (q *keyQueue) Frame(state keyState) keyState {
for k, h := range q.handlers {
if !h.visible || !h.focusable {
if q.focus == k {
if state.focus == k {
// Remove focus from the handler that is no longer focusable.
q.focus = nil
q.state = TextInputClose
state.focus = nil
state.state = TextInputClose
}
if !h.visible && !h.focusable {
delete(q.handlers, k)
@@ -126,6 +127,7 @@ func (q *keyQueue) Frame() {
h.active = false
}
q.updateFocusLayout()
return state
}
// updateFocusLayout partitions input handlers handlers into rows
@@ -168,13 +170,13 @@ func (q *keyQueue) updateFocusLayout() {
}
// MoveFocus attempts to move the focus in the direction of dir.
func (q *keyQueue) MoveFocus(evts []taggedEvent, dir key.FocusDirection) []taggedEvent {
func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState, []taggedEvent) {
if len(q.dirOrder) == 0 {
return nil
return state, nil
}
order := 0
if q.focus != nil {
order = q.handlers[q.focus].dirOrder
if state.focus != nil {
order = q.handlers[state.focus].dirOrder
}
focus := q.dirOrder[order]
switch dir {
@@ -186,8 +188,8 @@ func (q *keyQueue) MoveFocus(evts []taggedEvent, dir key.FocusDirection) []tagge
if dir == key.FocusBackward {
order = -1
}
if q.focus != nil {
order = q.handlers[q.focus].order
if state.focus != nil {
order = q.handlers[state.focus].order
if dir == key.FocusForward {
order++
} else {
@@ -195,10 +197,10 @@ func (q *keyQueue) MoveFocus(evts []taggedEvent, dir key.FocusDirection) []tagge
}
}
order = (order + len(q.order)) % len(q.order)
return q.Focus(evts, q.order[order])
return q.Focus(state, q.order[order])
case key.FocusRight, key.FocusLeft:
next := order
if q.focus != nil {
if state.focus != nil {
next = order + 1
if dir == key.FocusLeft {
next = order - 1
@@ -207,7 +209,7 @@ func (q *keyQueue) MoveFocus(evts []taggedEvent, dir key.FocusDirection) []tagge
if 0 <= next && next < len(q.dirOrder) {
newFocus := q.dirOrder[next]
if newFocus.row == focus.row {
return q.Focus(evts, newFocus.tag)
return q.Focus(state, newFocus.tag)
}
}
case key.FocusUp, key.FocusDown:
@@ -216,7 +218,7 @@ func (q *keyQueue) MoveFocus(evts []taggedEvent, dir key.FocusDirection) []tagge
delta = -1
}
nextRow := 0
if q.focus != nil {
if state.focus != nil {
nextRow = focus.row + delta
}
var closest event.Tag
@@ -243,10 +245,10 @@ func (q *keyQueue) MoveFocus(evts []taggedEvent, dir key.FocusDirection) []tagge
order += delta
}
if closest != nil {
return q.Focus(evts, closest)
return q.Focus(state, closest)
}
}
return nil
return state, nil
}
func (q *keyQueue) BoundsFor(t event.Tag) image.Rectangle {
@@ -281,35 +283,37 @@ func keyFilterMatch(f key.Filter, e key.Event) bool {
return true
}
func (q *keyQueue) Focus(evts []taggedEvent, focus event.Tag) []taggedEvent {
func (q *keyQueue) Focus(state keyState, focus event.Tag) (keyState, []taggedEvent) {
if focus != nil {
if _, exists := q.handlers[focus]; !exists {
focus = nil
}
}
if focus == q.focus {
return evts
if focus == state.focus {
return state, nil
}
q.content = EditorState{}
if q.focus != nil {
evts = append(evts, taggedEvent{tag: q.focus, event: key.FocusEvent{Focus: false}})
state.content = EditorState{}
var evts []taggedEvent
if state.focus != nil {
evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: false}})
}
q.focus = focus
if q.focus != nil {
evts = append(evts, taggedEvent{tag: q.focus, event: key.FocusEvent{Focus: true}})
state.focus = focus
if state.focus != nil {
evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: true}})
}
if q.focus == nil || q.state == TextInputKeep {
q.state = TextInputClose
if state.focus == nil || state.state == TextInputKeep {
state.state = TextInputClose
}
return evts
return state, evts
}
func (q *keyQueue) softKeyboard(show bool) {
func (s keyState) softKeyboard(show bool) keyState {
if show {
q.state = TextInputOpen
s.state = TextInputOpen
} else {
q.state = TextInputClose
s.state = TextInputClose
}
return s
}
func (q *keyQueue) filter(tag event.Tag, f key.Filter) {
@@ -349,26 +353,28 @@ func (q *keyQueue) inputOp(tag event.Tag, t f32.Affine2D, area int, bounds image
h.trans = t
}
func (q *keyQueue) setSelection(req key.SelectionCmd) {
if req.Tag != q.focus {
return
func (q *keyQueue) setSelection(state keyState, req key.SelectionCmd) keyState {
if req.Tag != state.focus {
return state
}
q.content.Selection.Range = req.Range
q.content.Selection.Caret = req.Caret
state.content.Selection.Range = req.Range
state.content.Selection.Caret = req.Caret
return state
}
func (q *keyQueue) editorState() EditorState {
s := q.content
if f := q.focus; f != nil {
func (q *keyQueue) editorState(state keyState) EditorState {
s := state.content
if f := state.focus; f != nil {
s.Selection.Transform = q.handlers[f].trans
}
return s
}
func (q *keyQueue) setSnippet(req key.SnippetCmd) {
if req.Tag == q.focus {
q.content.Snippet = req.Snippet
func (q *keyQueue) setSnippet(state keyState, req key.SnippetCmd) keyState {
if req.Tag == state.focus {
state.content.Snippet = req.Snippet
}
return state
}
func (t TextInputState) String() string {