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
+1 -1
View File
@@ -319,7 +319,7 @@ func (w *Window) processFrame(d driver) {
if mime, txt, ok := q.WriteClipboard(); ok {
d.WriteClipboard(mime, txt)
}
if q.ReadClipboard() {
if q.ClipboardRequested() {
d.ReadClipboard()
}
oldState := w.imeState
+24 -19
View File
@@ -9,8 +9,12 @@ import (
"gioui.org/io/event"
)
// clipboardState contains the state for clipboard event routing.
type clipboardState struct {
receivers []event.Tag
}
type clipboardQueue struct {
receivers map[event.Tag]struct{}
// request avoid read clipboard every frame while waiting.
requested bool
mime string
@@ -28,22 +32,21 @@ func (q *clipboardQueue) WriteClipboard() (mime string, content []byte, ok bool)
return q.mime, content, true
}
// ReadClipboard reports if any new handler is waiting
// ClipboardRequested reports if any new handler is waiting
// to read the clipboard.
func (q *clipboardQueue) ReadClipboard() bool {
if len(q.receivers) == 0 || q.requested {
return false
}
q.requested = true
return true
func (q *clipboardQueue) ClipboardRequested(state clipboardState) bool {
req := len(state.receivers) > 0 && q.requested
q.requested = false
return req
}
func (q *clipboardQueue) Push(evts []taggedEvent, e event.Event) []taggedEvent {
for r := range q.receivers {
func (q *clipboardQueue) Push(state clipboardState, e event.Event) (clipboardState, []taggedEvent) {
var evts []taggedEvent
for _, r := range state.receivers {
evts = append(evts, taggedEvent{tag: r, event: e})
delete(q.receivers, r)
}
return evts
state.receivers = nil
return state, evts
}
func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
@@ -56,12 +59,14 @@ func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
q.text = content
}
func (q *clipboardQueue) ProcessReadClipboard(tag event.Tag) {
if q.receivers == nil {
q.receivers = make(map[event.Tag]struct{})
}
if _, ok := q.receivers[tag]; !ok {
q.receivers[tag] = struct{}{}
q.requested = false
func (q *clipboardQueue) ProcessReadClipboard(state clipboardState, tag event.Tag) clipboardState {
for _, k := range state.receivers {
if k == tag {
return state
}
}
n := len(state.receivers)
state.receivers = append(state.receivers[:n:n], tag)
q.requested = true
return state
}
+7 -7
View File
@@ -28,9 +28,9 @@ func TestClipboardDuplicateEvent(t *testing.T) {
},
}
router.Queue(event)
assertClipboardReadCmd(t, router, 0)
assertClipboardEvent(t, router.Events(&handler[0], transfer.TargetFilter{Type: "application/text"}), true)
assertClipboardEvent(t, router.Events(&handler[1], transfer.TargetFilter{Type: "application/text"}), true)
assertClipboardReadCmd(t, router, 0)
ops.Reset()
// No ReadCmd
@@ -80,8 +80,8 @@ func TestQueueProcessReadClipboard(t *testing.T) {
},
}
router.Queue(event)
assertClipboardReadCmd(t, router, 0)
assertClipboardEvent(t, router.Events(&handler[0], transfer.TargetFilter{Type: "application/text"}), true)
assertClipboardReadCmd(t, router, 0)
ops.Reset()
// No ReadCmd
@@ -137,20 +137,20 @@ func assertClipboardEvent(t *testing.T, events []event.Event, expected bool) {
func assertClipboardReadCmd(t *testing.T, router *Router, expected int) {
t.Helper()
if len(router.cqueue.receivers) != expected {
t.Error("unexpected number of receivers")
if got := len(router.lastState().receivers); got != expected {
t.Errorf("unexpected %d receivers, got %d", expected, got)
}
if router.cqueue.ReadClipboard() != (expected > 0) {
if router.ClipboardRequested() != (expected > 0) {
t.Error("missing requests")
}
}
func assertClipboardReadDuplicated(t *testing.T, router *Router, expected int) {
t.Helper()
if len(router.cqueue.receivers) != expected {
if len(router.lastState().receivers) != expected {
t.Error("receivers removed")
}
if router.cqueue.ReadClipboard() != false {
if router.ClipboardRequested() != false {
t.Error("duplicated requests")
}
}
+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 {
+2 -2
View File
@@ -464,14 +464,14 @@ func assertKeyEventUnexpected(t *testing.T, events []event.Event) {
func assertFocus(t *testing.T, router *Router, expected event.Tag) {
t.Helper()
if got := router.key.queue.focus; got != expected {
if got := router.lastState().focus; got != expected {
t.Errorf("expected %v to be focused, got %v", expected, got)
}
}
func assertKeyboard(t *testing.T, router *Router, expected TextInputState) {
t.Helper()
if got := router.key.queue.state; got != expected {
if got := router.lastState().state; got != expected {
t.Errorf("expected %v keyboard, got %v", expected, got)
}
}
+82 -71
View File
@@ -17,12 +17,9 @@ import (
)
type pointerQueue struct {
hitTree []hitNode
areas []areaNode
cursor pointer.Cursor
handlers map[event.Tag]*pointerHandler
pointers []pointerInfo
transfers []io.ReadCloser // pending data transfers
hitTree []hitNode
areas []areaNode
handlers map[event.Tag]*pointerHandler
semantic struct {
idsAssigned bool
@@ -43,6 +40,12 @@ type hitNode struct {
pass bool
}
// pointerState is the input state related to pointer events.
type pointerState struct {
cursor pointer.Cursor
pointers []pointerInfo
}
type pointerInfo struct {
id pointer.ID
pressed bool
@@ -264,8 +267,9 @@ func (c *pointerCollector) actionInputOp(act system.Action) {
area.action = act
}
func (q *pointerQueue) grab(evts []taggedEvent, req pointer.GrabCmd) []taggedEvent {
for _, p := range q.pointers {
func (q *pointerQueue) grab(state pointerState, req pointer.GrabCmd) (pointerState, []taggedEvent) {
var evts []taggedEvent
for _, p := range state.pointers {
if !p.pressed || p.id != req.ID {
continue
}
@@ -276,12 +280,12 @@ func (q *pointerQueue) grab(evts []taggedEvent, req pointer.GrabCmd) []taggedEve
tag: tag,
event: pointer.Event{Kind: pointer.Cancel},
})
q.dropHandler(tag)
state = dropHandler(state, tag)
}
}
break
}
return evts
return state, evts
}
func (c *pointerCollector) inputOp(tag event.Tag) {
@@ -357,29 +361,25 @@ func (q *pointerQueue) targetFilter(tag event.Tag, f transfer.TargetFilter) {
h.targetMimes = append(h.targetMimes, f.Type)
}
func (q *pointerQueue) offerData(evts []taggedEvent, req transfer.OfferCmd) []taggedEvent {
transferIdx := len(q.transfers)
q.transfers = append(q.transfers, req.Data)
for i := range q.pointers {
p := q.pointers[i]
func (q *pointerQueue) offerData(state pointerState, req transfer.OfferCmd) (pointerState, []taggedEvent) {
var evts []taggedEvent
for i, p := range state.pointers {
if p.dataSource != req.Tag {
continue
}
if p.dataTarget == nil {
q.pointers[i], evts = q.deliverTransferCancelEvent(p, evts)
break
if p.dataTarget != nil {
evts = append(evts, taggedEvent{tag: p.dataTarget, event: transfer.DataEvent{
Type: req.Type,
Open: func() io.ReadCloser {
return req.Data
},
}})
}
evts = append(evts, taggedEvent{tag: p.dataTarget, event: transfer.DataEvent{
Type: req.Type,
Open: func() io.ReadCloser {
q.transfers[transferIdx] = nil
return req.Data
},
}})
q.pointers[i], evts = q.deliverTransferCancelEvent(p, evts)
state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[i], evts = q.deliverTransferCancelEvent(p, evts)
break
}
return evts
return state, evts
}
func (c *pointerCollector) reset() {
@@ -595,18 +595,12 @@ func (q *pointerQueue) reset() {
delete(q.semantic.contentIDs, k)
}
}
for _, rc := range q.transfers {
if rc != nil {
rc.Close()
}
}
q.transfers = nil
}
func (q *pointerQueue) Frame(evts []taggedEvent) []taggedEvent {
func (q *pointerQueue) Frame(state pointerState) (pointerState, []taggedEvent) {
for k, h := range q.handlers {
if !h.active {
q.dropHandler(k)
state = dropHandler(state, k)
delete(q.handlers, k)
continue
}
@@ -622,38 +616,51 @@ func (q *pointerQueue) Frame(evts []taggedEvent) []taggedEvent {
area.semantic.valid = area.semantic.content.gestures != 0
}
}
for i := range q.pointers {
p := q.pointers[i]
q.pointers[i], evts = q.deliverEnterLeaveEvents(p, evts, p.last)
var evts []taggedEvent
for i, p := range state.pointers {
changed := false
p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(state.cursor, p, evts, p.last)
if changed {
state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[i] = p
}
}
return evts
return state, evts
}
func (q *pointerQueue) dropHandler(tag event.Tag) {
for i := range q.pointers {
p := &q.pointers[i]
for i := len(p.handlers) - 1; i >= 0; i-- {
if p.handlers[i] == tag {
p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
func dropHandler(state pointerState, tag event.Tag) pointerState {
pointers := state.pointers
state.pointers = nil
for _, p := range pointers {
handlers := p.handlers
p.handlers = nil
for _, h := range handlers {
if h != tag {
p.handlers = append(p.handlers, h)
}
}
for i := len(p.entered) - 1; i >= 0; i-- {
if p.entered[i] == tag {
p.entered = append(p.entered[:i], p.entered[i+1:]...)
entered := p.entered
p.entered = nil
for _, h := range entered {
if h != tag {
p.entered = append(p.entered, h)
}
}
state.pointers = append(state.pointers, p)
}
return state
}
// pointerOf returns the pointerInfo index corresponding to the pointer in e.
func (q *pointerQueue) pointerOf(e pointer.Event) int {
for i, p := range q.pointers {
func (s pointerState) pointerOf(e pointer.Event) (pointerState, int) {
for i, p := range s.pointers {
if p.id == e.PointerID {
return i
return s, i
}
}
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
return len(q.pointers) - 1
n := len(s.pointers)
s.pointers = append(s.pointers[:n:n], pointerInfo{id: e.PointerID})
return s, len(s.pointers) - 1
}
// Deliver is like Push, but delivers an event to a particular area.
@@ -710,7 +717,8 @@ func (q *pointerQueue) SemanticArea(areaIdx int) (semanticContent, int) {
return semanticContent{}, -1
}
func (q *pointerQueue) Push(evts []taggedEvent, e pointer.Event) []taggedEvent {
func (q *pointerQueue) Push(state pointerState, e pointer.Event) (pointerState, []taggedEvent) {
var evts []taggedEvent
if e.Kind == pointer.Cancel {
for k := range q.handlers {
evts = append(evts, taggedEvent{
@@ -718,25 +726,22 @@ func (q *pointerQueue) Push(evts []taggedEvent, e pointer.Event) []taggedEvent {
tag: k,
})
}
q.pointers = q.pointers[:0]
for k := range q.handlers {
q.dropHandler(k)
}
return evts
state.pointers = nil
return state, evts
}
pidx := q.pointerOf(e)
p := q.pointers[pidx]
state, pidx := state.pointerOf(e)
p := state.pointers[pidx]
switch e.Kind {
case pointer.Press:
p, evts = q.deliverEnterLeaveEvents(p, evts, e)
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e)
p.pressed = true
evts = q.deliverEvent(p, evts, e)
case pointer.Move:
if p.pressed {
e.Kind = pointer.Drag
}
p, evts = q.deliverEnterLeaveEvents(p, evts, e)
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e)
evts = q.deliverEvent(p, evts, e)
if p.pressed {
p, evts = q.deliverDragEvent(p, evts)
@@ -744,23 +749,25 @@ func (q *pointerQueue) Push(evts []taggedEvent, e pointer.Event) []taggedEvent {
case pointer.Release:
evts = q.deliverEvent(p, evts, e)
p.pressed = false
p, evts = q.deliverEnterLeaveEvents(p, evts, e)
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e)
p, evts = q.deliverDropEvent(p, evts)
case pointer.Scroll:
p, evts = q.deliverEnterLeaveEvents(p, evts, e)
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e)
evts = q.deliverEvent(p, evts, e)
default:
panic("unsupported pointer event type")
}
q.pointers[pidx] = p
p.last = e
if !p.pressed && len(p.entered) == 0 {
// No longer need to track pointer.
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
state.pointers = append(state.pointers[:pidx:pidx], state.pointers[pidx+1:]...)
} else {
state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[pidx] = p
}
return evts
return state, evts
}
func (q *pointerQueue) deliverEvent(p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent {
@@ -794,12 +801,13 @@ func (q *pointerQueue) deliverEvent(p pointerInfo, evts []taggedEvent, e pointer
return evts
}
func (q *pointerQueue) deliverEnterLeaveEvents(p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent) {
func (q *pointerQueue) deliverEnterLeaveEvents(cursor pointer.Cursor, p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent, pointer.Cursor, bool) {
changed := false
var hits []event.Tag
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
// Consider non-mouse pointers leaving when they're released.
} else {
hits, q.cursor = q.opHit(e.Position)
hits, cursor = q.opHit(e.Position)
if p.pressed {
// Filter out non-participating handlers,
// except potential transfer targets when a transfer has been initiated.
@@ -819,6 +827,7 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p pointerInfo, evts []taggedEvent
}
}
} else {
changed = true
p.handlers = hits
}
}
@@ -827,6 +836,7 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p pointerInfo, evts []taggedEvent
if _, found := searchTag(hits, k); found {
continue
}
changed = true
h := q.handlers[k]
e := e
e.Kind = pointer.Leave
@@ -842,6 +852,7 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p pointerInfo, evts []taggedEvent
if _, found := searchTag(p.entered, k); found {
continue
}
changed = true
e := e
e.Kind = pointer.Enter
@@ -851,7 +862,7 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p pointerInfo, evts []taggedEvent
}
}
p.entered = hits
return p, evts
return p, evts, cursor, changed
}
func (q *pointerQueue) deliverDragEvent(p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
+1
View File
@@ -1037,6 +1037,7 @@ func TestPassCursor(t *testing.T) {
Position: f32.Pt(10, 10),
Kind: pointer.Move,
})
r.Frame(&ops)
if got := r.Cursor(); want != got {
t.Errorf("got cursor %v, want %v", got, want)
}
+211 -74
View File
@@ -5,6 +5,7 @@ package input
import (
"encoding/binary"
"image"
"io"
"strings"
"time"
@@ -35,7 +36,10 @@ type Router struct {
}
cqueue clipboardQueue
events []taggedEvent
// states is the list of pending state changes resulting from
// incoming events. The first element is the current state,
// if any.
changes []stateChange
reader ops.Reader
@@ -45,6 +49,9 @@ type Router struct {
// Changes queued for next call to Frame.
commands []Command
// transfers is the pending transfer.DataEvent.Open functions.
transfers []io.ReadCloser
}
// Source implements the interface between a Router and user interface widgets.
@@ -94,6 +101,21 @@ const (
// By convention, the zero value denotes the non-existent ID.
type SemanticID uint
// stateChange represents the new state and outgoing events
// resulting from an incoming event.
type stateChange struct {
state inputState
events []taggedEvent
}
// inputState represent a immutable snapshot of the state required
// to route events.
type inputState struct {
clipboardState
keyState
pointerState
}
// taggedEvent represents an event and its target handler.
type taggedEvent struct {
event event.Event
@@ -130,7 +152,8 @@ func (s Source) Events(k event.Tag, filters ...event.Filter) []event.Event {
}
func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event {
var evts []event.Event
var events []event.Event
// Record handler filters and add reset events.
for _, f := range filters {
switch f := f.(type) {
case key.Filter:
@@ -138,12 +161,12 @@ func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event {
case key.FocusFilter:
q.key.queue.focusable(k)
if reset, ok := q.key.queue.ResetEvent(k); ok {
evts = append(evts, reset)
events = append(events, reset)
}
case pointer.Filter:
q.pointer.queue.filterTag(k, f)
if reset, ok := q.pointer.queue.ResetEvent(k); ok {
evts = append(evts, reset)
events = append(events, reset)
}
case transfer.SourceFilter:
q.pointer.queue.sourceFilter(k, f)
@@ -151,39 +174,71 @@ func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event {
q.pointer.queue.targetFilter(k, f)
}
}
i := 0
for i < len(q.events) {
e := q.events[i]
if e.tag == k {
q.events = append(q.events[:i], q.events[i+1:]...)
if filtersMatches(filters, e.event) {
evts = append(evts, e.event)
// Accumulate events from state changes until there are no more
// matching events.
matchedIdx := 0
for i := range q.changes {
change := &q.changes[i]
j := 0
for j < len(change.events) {
evt := change.events[j]
if evt.tag != k || !filtersMatches(filters, evt.event) {
j++
continue
}
} else {
i++
events = append(events, evt.event)
change.events = append(change.events[:j], change.events[j+1:]...)
matchedIdx = i
}
}
return evts
// Fast forward state to last matched.
q.collapseState(matchedIdx)
return events
}
// collapseState in the interval [1;idx] into q.changes[0].
func (q *Router) collapseState(idx int) {
if idx == 0 {
return
}
first := &q.changes[0]
first.state = q.changes[idx].state
for i := 1; i <= idx; i++ {
first.events = append(first.events, q.changes[i].events...)
}
q.changes = append(q.changes[:1], q.changes[idx+1:]...)
}
// Frame replaces the declared handlers from the supplied
// operation list. The text input state, wakeup time and whether
// there are active profile handlers is also saved.
func (q *Router) Frame(frame *op.Ops) {
q.events = q.events[:0]
for _, rc := range q.transfers {
if rc != nil {
rc.Close()
}
}
q.transfers = nil
q.wakeup = false
// Collapse state and clear events.
if n := len(q.changes); n > 1 {
state := q.changes[n-1].state
q.changes = append(q.changes[:0], stateChange{state: state})
}
var ops *ops.Ops
if frame != nil {
ops = &frame.Internal
}
q.reader.Reset(ops)
q.collect()
evts := q.executeCommands(nil)
evts = q.pointer.queue.Frame(evts)
q.key.queue.Frame()
q.addEvents(evts)
q.executeCommands()
q.changePointerState(q.pointer.queue.Frame(q.lastState().pointerState))
kstate := q.key.queue.Frame(q.lastState().keyState)
q.changeKeyState(kstate, nil)
// Collapse state and events.
q.collapseState(len(q.changes) - 1)
if len(evts) > 0 {
if len(q.changes) > 0 && len(q.changes[0].events) > 0 {
q.wakeup = true
q.wakeupTime = time.Time{}
}
@@ -191,69 +246,147 @@ func (q *Router) Frame(frame *op.Ops) {
// Queue events and report whether at least one event matched a handler.
func (q *Router) Queue(events ...event.Event) bool {
var evts []taggedEvent
matched := false
for _, e := range events {
switch e := e.(type) {
case pointer.Event:
evts = q.pointer.queue.Push(evts, e)
case key.Event:
evts = q.queueKeyEvent(evts, e)
case key.SnippetEvent:
// Expand existing, overlapping snippet.
if r := q.key.queue.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
if e.Start > r.Start {
e.Start = r.Start
}
if e.End < r.End {
e.End = r.End
}
}
if f := q.key.queue.focus; f != nil {
evts = append(evts, taggedEvent{tag: f, event: e})
}
case key.EditEvent, key.FocusEvent, key.SelectionEvent:
if f := q.key.queue.focus; f != nil {
evts = append(evts, taggedEvent{tag: f, event: e})
}
case transfer.DataEvent:
evts = q.cqueue.Push(evts, e)
}
hadEvents := q.processEvent(e)
matched = matched || hadEvents
}
return matched
}
func (q *Router) processEvent(e event.Event) bool {
state := q.lastState()
switch e := e.(type) {
case pointer.Event:
return q.changePointerState(q.pointer.queue.Push(state.pointerState, e))
case key.Event:
return q.addEvents(q.queueKeyEvent(state.keyState, e))
case key.SnippetEvent:
// Expand existing, overlapping snippet.
if r := state.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
if e.Start > r.Start {
e.Start = r.Start
}
if e.End < r.End {
e.End = r.End
}
}
var evts []taggedEvent
if f := state.focus; f != nil {
evts = append(evts, taggedEvent{tag: f, event: e})
}
return q.addEvents(evts)
case key.EditEvent, key.FocusEvent, key.SelectionEvent:
var evts []taggedEvent
if f := state.focus; f != nil {
evts = append(evts, taggedEvent{tag: f, event: e})
}
return q.addEvents(evts)
case transfer.DataEvent:
return q.changeClipboardState(q.cqueue.Push(state.clipboardState, e))
default:
panic("unknown event type")
}
q.addEvents(evts)
return len(evts) > 0
}
func (q *Router) queue(f Command) {
q.commands = append(q.commands, f)
}
func (q *Router) executeCommands(evts []taggedEvent) []taggedEvent {
func (q *Router) state() inputState {
if len(q.changes) > 0 {
return q.changes[0].state
}
return inputState{}
}
func (q *Router) lastState() inputState {
if n := len(q.changes); n > 0 {
return q.changes[n-1].state
}
return inputState{}
}
func (q *Router) changeClipboardState(cstate clipboardState, evts []taggedEvent) bool {
state := q.lastState()
state.clipboardState = cstate
return q.changeState(state, evts)
}
func (q *Router) changeKeyState(kstate keyState, evts []taggedEvent) bool {
state := q.lastState()
state.keyState = kstate
return q.changeState(state, evts)
}
func (q *Router) changePointerState(pstate pointerState, evts []taggedEvent) bool {
state := q.lastState()
state.pointerState = pstate
return q.changeState(state, evts)
}
func (q *Router) executeCommands() {
for _, req := range q.commands {
state := q.lastState()
switch req := req.(type) {
case key.SelectionCmd:
q.key.queue.setSelection(req)
kstate := q.key.queue.setSelection(state.keyState, req)
q.changeKeyState(kstate, nil)
case key.FocusCmd:
evts = q.key.queue.Focus(evts, req.Tag)
q.changeKeyState(q.key.queue.Focus(state.keyState, req.Tag))
case key.SoftKeyboardCmd:
q.key.queue.softKeyboard(req.Show)
kstate := state.keyState.softKeyboard(req.Show)
q.changeKeyState(kstate, nil)
case key.SnippetCmd:
q.key.queue.setSnippet(req)
kstate := q.key.queue.setSnippet(state.keyState, req)
q.changeKeyState(kstate, nil)
case transfer.OfferCmd:
evts = q.pointer.queue.offerData(evts, req)
q.changePointerState(q.pointer.queue.offerData(state.pointerState, req))
case clipboard.WriteCmd:
q.cqueue.ProcessWriteClipboard(req)
case clipboard.ReadCmd:
q.cqueue.ProcessReadClipboard(req.Tag)
cstate := q.cqueue.ProcessReadClipboard(state.clipboardState, req.Tag)
q.changeClipboardState(cstate, nil)
case pointer.GrabCmd:
evts = q.pointer.queue.grab(evts, req)
q.changePointerState(q.pointer.queue.grab(state.pointerState, req))
}
}
q.commands = nil
return evts
}
func (q *Router) addEvents(evts []taggedEvent) {
q.events = append(q.events, evts...)
func (q *Router) addEvents(evts []taggedEvent) bool {
return q.changeState(q.lastState(), evts)
}
func (q *Router) changeState(state inputState, evts []taggedEvent) bool {
// Wrap pointer.DataEvent.Open functions to detect them not being called.
for i := range evts {
e := &evts[i]
if de, ok := e.event.(transfer.DataEvent); ok {
transferIdx := len(q.transfers)
data := de.Open()
q.transfers = append(q.transfers, data)
de.Open = func() io.ReadCloser {
q.transfers[transferIdx] = nil
return data
}
e.event = de
}
}
n := len(q.changes)
// We must add a new state change if
//
// - there is no first state change, or
// - the state change is not atomic from the perspective of the handlers.
if len(q.changes) == 0 || (len(evts) > 0 && len(q.changes[n-1].events) > 0) {
q.changes = append(q.changes, stateChange{state: state, events: evts})
} else {
// Otherwise, merge with previous change.
prev := &q.changes[n-1]
prev.state = state
prev.events = append(prev.events, evts...)
}
return len(evts) > 0
}
func rangeOverlaps(r1, r2 key.Range) bool {
@@ -270,9 +403,10 @@ func rangeNorm(r key.Range) key.Range {
return r
}
func (q *Router) queueKeyEvent(evts []taggedEvent, e key.Event) []taggedEvent {
func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent {
kq := &q.key.queue
f := q.key.queue.focus
f := state.focus
var evts []taggedEvent
if f != nil && kq.Accepts(f, e) {
evts = append(evts, taggedEvent{tag: f, event: e})
return evts
@@ -305,15 +439,15 @@ func (q *Router) queueKeyEvent(evts []taggedEvent, e key.Event) []taggedEvent {
}
func (q *Router) MoveFocus(dir key.FocusDirection) bool {
evts := q.key.queue.MoveFocus(nil, dir)
q.addEvents(evts)
return len(evts) > 0
ks, evts := q.key.queue.MoveFocus(q.lastState().keyState, dir)
return q.changeKeyState(ks, evts)
}
// RevealFocus scrolls the current focus (if any) into viewport
// if there are scrollable parent handlers.
func (q *Router) RevealFocus(viewport image.Rectangle) {
focus := q.key.queue.focus
state := q.lastState()
focus := state.focus
if focus == nil {
return
}
@@ -339,7 +473,8 @@ func (q *Router) RevealFocus(viewport image.Rectangle) {
// ScrollFocus scrolls the focused widget, if any, by dist.
func (q *Router) ScrollFocus(dist image.Point) {
focus := q.key.queue.focus
state := q.lastState()
focus := state.focus
if focus == nil {
return
}
@@ -378,7 +513,7 @@ func (q *Router) ActionAt(p f32.Point) (system.Action, bool) {
}
func (q *Router) ClickFocus() {
focus := q.key.queue.focus
focus := q.lastState().focus
if focus == nil {
return
}
@@ -398,12 +533,14 @@ func (q *Router) ClickFocus() {
// TextInputState returns the input state from the most recent
// call to Frame.
func (q *Router) TextInputState() TextInputState {
return q.key.queue.InputState()
kstate, s := q.state().InputState()
q.changeKeyState(kstate, nil)
return s
}
// TextInputHint returns the input mode from the most recent key.InputOp.
func (q *Router) TextInputHint() (key.InputHint, bool) {
return q.key.queue.InputHint()
return q.key.queue.InputHint(q.state().keyState)
}
// WriteClipboard returns the most recent content to be copied
@@ -412,15 +549,15 @@ func (q *Router) WriteClipboard() (mime string, content []byte, ok bool) {
return q.cqueue.WriteClipboard()
}
// ReadClipboard reports if any new handler is waiting
// ClipboardRequested reports if any new handler is waiting
// to read the clipboard.
func (q *Router) ReadClipboard() bool {
return q.cqueue.ReadClipboard()
func (q *Router) ClipboardRequested() bool {
return q.cqueue.ClipboardRequested(q.lastState().clipboardState)
}
// Cursor returns the last cursor set.
func (q *Router) Cursor() pointer.Cursor {
return q.pointer.queue.cursor
return q.state().cursor
}
// SemanticAt returns the first semantic description under pos, if any.
@@ -439,7 +576,7 @@ func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode {
// EditorState returns the editor state for the focused handler, or the
// zero value if there is none.
func (q *Router) EditorState() EditorState {
return q.key.queue.editorState()
return q.key.queue.editorState(q.state().keyState)
}
func (q *Router) collect() {