forked from joejulian/gio
io/input: [API] execute commands immediately
Change the semantics of commands to execute immediately. In cases where execution of a command introduces a inconsistency, freeze event routing and defer the command as well as queued events to the next frame. Rename Source.Queue to Source.Execute to better fit the new command semantics. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -17,8 +17,8 @@ func TestClipboardDuplicateEvent(t *testing.T) {
|
||||
ops, router, handler := new(op.Ops), new(Router), make([]int, 2)
|
||||
|
||||
// Both must receive the event once
|
||||
router.Source().Queue(clipboard.ReadCmd{Tag: &handler[0]})
|
||||
router.Source().Queue(clipboard.ReadCmd{Tag: &handler[1]})
|
||||
router.Source().Execute(clipboard.ReadCmd{Tag: &handler[0]})
|
||||
router.Source().Execute(clipboard.ReadCmd{Tag: &handler[1]})
|
||||
|
||||
router.Frame(ops)
|
||||
event := transfer.DataEvent{
|
||||
@@ -41,7 +41,7 @@ func TestClipboardDuplicateEvent(t *testing.T) {
|
||||
assertClipboardEvent(t, router.Events(&handler[1]), false)
|
||||
ops.Reset()
|
||||
|
||||
router.Source().Queue(clipboard.ReadCmd{Tag: &handler[0]})
|
||||
router.Source().Execute(clipboard.ReadCmd{Tag: &handler[0]})
|
||||
|
||||
router.Frame(ops)
|
||||
// No ClipboardEvent sent
|
||||
@@ -56,7 +56,7 @@ func TestQueueProcessReadClipboard(t *testing.T) {
|
||||
ops.Reset()
|
||||
|
||||
// Request read
|
||||
router.Source().Queue(clipboard.ReadCmd{Tag: &handler[0]})
|
||||
router.Source().Execute(clipboard.ReadCmd{Tag: &handler[0]})
|
||||
|
||||
router.Frame(ops)
|
||||
assertClipboardReadCmd(t, router, 1)
|
||||
@@ -94,28 +94,18 @@ func TestQueueProcessReadClipboard(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQueueProcessWriteClipboard(t *testing.T) {
|
||||
ops, router := new(op.Ops), new(Router)
|
||||
ops.Reset()
|
||||
router := new(Router)
|
||||
|
||||
const mime = "application/text"
|
||||
router.Source().Queue(clipboard.WriteCmd{Type: mime, Data: io.NopCloser(strings.NewReader("Write 1"))})
|
||||
router.Source().Execute(clipboard.WriteCmd{Type: mime, Data: io.NopCloser(strings.NewReader("Write 1"))})
|
||||
|
||||
router.Frame(ops)
|
||||
assertClipboardWriteCmd(t, router, mime, "Write 1")
|
||||
ops.Reset()
|
||||
|
||||
// No WriteCmd
|
||||
|
||||
router.Frame(ops)
|
||||
assertClipboardWriteCmd(t, router, "", "")
|
||||
ops.Reset()
|
||||
|
||||
router.Source().Queue(clipboard.WriteCmd{Type: mime, Data: io.NopCloser(strings.NewReader("Write 2"))})
|
||||
router.Source().Execute(clipboard.WriteCmd{Type: mime, Data: io.NopCloser(strings.NewReader("Write 2"))})
|
||||
|
||||
router.Frame(ops)
|
||||
assertClipboardReadCmd(t, router, 0)
|
||||
assertClipboardWriteCmd(t, router, mime, "Write 2")
|
||||
ops.Reset()
|
||||
}
|
||||
|
||||
func assertClipboardEvent(t *testing.T, events []event.Event, expected bool) {
|
||||
|
||||
+20
-20
@@ -37,9 +37,8 @@ func TestKeyMultiples(t *testing.T) {
|
||||
ops := new(op.Ops)
|
||||
r := new(Router)
|
||||
|
||||
r.Source().Queue(key.SoftKeyboardCmd{Show: true})
|
||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
||||
event.InputOp(ops, &handlers[0])
|
||||
r.Source().Queue(key.FocusCmd{Tag: &handlers[2]})
|
||||
event.InputOp(ops, &handlers[1])
|
||||
|
||||
// The last one must be focused:
|
||||
@@ -51,6 +50,7 @@ func TestKeyMultiples(t *testing.T) {
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[2]})
|
||||
assertKeyEvent(t, r.Events(&handlers[2], key.FocusFilter{}), true)
|
||||
assertFocus(t, r, &handlers[2])
|
||||
|
||||
@@ -63,12 +63,12 @@ func TestKeyStacked(t *testing.T) {
|
||||
r := new(Router)
|
||||
|
||||
event.InputOp(ops, &handlers[0])
|
||||
r.Source().Queue(key.FocusCmd{})
|
||||
r.Source().Queue(key.SoftKeyboardCmd{Show: false})
|
||||
r.Source().Execute(key.FocusCmd{})
|
||||
r.Source().Execute(key.SoftKeyboardCmd{Show: false})
|
||||
event.InputOp(ops, &handlers[1])
|
||||
r.Source().Queue(key.FocusCmd{Tag: &handlers[1]})
|
||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[1]})
|
||||
event.InputOp(ops, &handlers[2])
|
||||
r.Source().Queue(key.SoftKeyboardCmd{Show: true})
|
||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
||||
event.InputOp(ops, &handlers[3])
|
||||
|
||||
for i := range handlers {
|
||||
@@ -88,7 +88,7 @@ func TestKeySoftKeyboardNoFocus(t *testing.T) {
|
||||
|
||||
// It's possible to open the keyboard
|
||||
// without any active focus:
|
||||
r.Source().Queue(key.SoftKeyboardCmd{Show: true})
|
||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -103,8 +103,8 @@ func TestKeyRemoveFocus(t *testing.T) {
|
||||
|
||||
// New InputOp with Focus and Keyboard:
|
||||
event.InputOp(ops, &handlers[0])
|
||||
r.Source().Queue(key.FocusCmd{Tag: &handlers[0]})
|
||||
r.Source().Queue(key.SoftKeyboardCmd{Show: true})
|
||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
||||
|
||||
// New InputOp without any focus:
|
||||
event.InputOp(ops, &handlers[1])
|
||||
@@ -136,7 +136,7 @@ func TestKeyRemoveFocus(t *testing.T) {
|
||||
event.InputOp(ops, &handlers[1])
|
||||
|
||||
// Remove focus by focusing on a tag that don't exist.
|
||||
r.Source().Queue(key.FocusCmd{Tag: new(int)})
|
||||
r.Source().Execute(key.FocusCmd{Tag: new(int)})
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -149,26 +149,26 @@ func TestKeyRemoveFocus(t *testing.T) {
|
||||
event.InputOp(ops, &handlers[0])
|
||||
event.InputOp(ops, &handlers[1])
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[0], key.FocusFilter{}))
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
|
||||
assertFocus(t, r, nil)
|
||||
assertKeyboard(t, r, TextInputClose)
|
||||
|
||||
r.Frame(ops)
|
||||
ops.Reset()
|
||||
|
||||
// Set focus to InputOp which already
|
||||
// exists in the previous frame:
|
||||
r.Source().Queue(key.FocusCmd{Tag: &handlers[0]})
|
||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
||||
event.InputOp(ops, &handlers[0])
|
||||
r.Source().Queue(key.SoftKeyboardCmd{Show: true})
|
||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
||||
assertFocus(t, r, &handlers[0])
|
||||
|
||||
ops.Reset()
|
||||
|
||||
// Remove focus.
|
||||
event.InputOp(ops, &handlers[1])
|
||||
r.Source().Queue(key.FocusCmd{})
|
||||
|
||||
r.Frame(ops)
|
||||
r.Source().Execute(key.FocusCmd{})
|
||||
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
|
||||
assertFocus(t, r, nil)
|
||||
@@ -181,9 +181,9 @@ func TestKeyFocusedInvisible(t *testing.T) {
|
||||
r := new(Router)
|
||||
|
||||
// Set new InputOp with focus:
|
||||
r.Source().Queue(key.FocusCmd{Tag: &handlers[0]})
|
||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
||||
event.InputOp(ops, &handlers[0])
|
||||
r.Source().Queue(key.SoftKeyboardCmd{Show: true})
|
||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
||||
|
||||
// Set new InputOp without focus:
|
||||
event.InputOp(ops, &handlers[1])
|
||||
@@ -403,7 +403,7 @@ func TestKeyRouting(t *testing.T) {
|
||||
r2.Events(&handlers[3], key.FocusFilter{})
|
||||
r2.Events(&handlers[4], fa...)
|
||||
|
||||
r2.Source().Queue(key.FocusCmd{Tag: &handlers[3]})
|
||||
r2.Source().Execute(key.FocusCmd{Tag: &handlers[3]})
|
||||
r2.Frame(ops)
|
||||
|
||||
r2.Queue(A, B)
|
||||
|
||||
@@ -99,10 +99,10 @@ func TestPointerGrab(t *testing.T) {
|
||||
Position: f32.Pt(50, 50),
|
||||
},
|
||||
)
|
||||
r.Source().Queue(pointer.GrabCmd{Tag: handler1})
|
||||
assertEventPointerTypeSequence(t, r.Events(handler1, filter), pointer.Press)
|
||||
assertEventPointerTypeSequence(t, r.Events(handler2, filter), pointer.Press)
|
||||
assertEventPointerTypeSequence(t, r.Events(handler3, filter), pointer.Press)
|
||||
r.Source().Execute(pointer.GrabCmd{Tag: handler1})
|
||||
r.Frame(&ops)
|
||||
r.Queue(
|
||||
pointer.Event{
|
||||
@@ -136,9 +136,9 @@ func TestPointerGrabSameHandlerTwice(t *testing.T) {
|
||||
Position: f32.Pt(50, 50),
|
||||
},
|
||||
)
|
||||
r.Source().Queue(pointer.GrabCmd{Tag: handler1})
|
||||
assertEventPointerTypeSequence(t, r.Events(handler1, filter), pointer.Press)
|
||||
assertEventPointerTypeSequence(t, r.Events(handler2, filter), pointer.Press)
|
||||
r.Source().Execute(pointer.GrabCmd{Tag: handler1})
|
||||
r.Frame(&ops)
|
||||
r.Queue(
|
||||
pointer.Event{
|
||||
@@ -941,7 +941,7 @@ func TestTransfer(t *testing.T) {
|
||||
|
||||
// Offer valid type and data.
|
||||
ofr := &offer{data: "hello"}
|
||||
r.Source().Queue(transfer.OfferCmd{Tag: src, Type: "file", Data: ofr})
|
||||
r.Source().Execute(transfer.OfferCmd{Tag: src, Type: "file", Data: ofr})
|
||||
r.Frame(ops)
|
||||
assertEventSequence(t, r.Events(src, transfer.SourceFilter{Type: "file"}), transfer.CancelEvent{})
|
||||
evs := r.Events(tgt, transfer.TargetFilter{Type: "file"})
|
||||
@@ -989,9 +989,9 @@ func TestTransfer(t *testing.T) {
|
||||
},
|
||||
)
|
||||
ofr := &offer{data: "hello"}
|
||||
r.Source().Queue(transfer.OfferCmd{Tag: src, Type: "file", Data: ofr})
|
||||
r.Events(src, transfer.SourceFilter{Type: "file"})
|
||||
r.Events(tgt, transfer.TargetFilter{Type: "file"})
|
||||
r.Source().Execute(transfer.OfferCmd{Tag: src, Type: "file", Data: ofr})
|
||||
r.Frame(ops)
|
||||
assertEventSequence(t, r.Events(src, transfer.SourceFilter{Type: "file"}), transfer.CancelEvent{})
|
||||
// Ignore DataEvent and verify that the next frame closes it as unused.
|
||||
|
||||
+142
-83
@@ -38,8 +38,8 @@ type Router struct {
|
||||
cqueue clipboardQueue
|
||||
|
||||
// states is the list of pending state changes resulting from
|
||||
// incoming events. The first element is the current state,
|
||||
// if any.
|
||||
// incoming events. The first element, if present, contains the state
|
||||
// and events for the current frame.
|
||||
changes []stateChange
|
||||
|
||||
reader ops.Reader
|
||||
@@ -54,6 +54,10 @@ type Router struct {
|
||||
// transfers is the pending transfer.DataEvent.Open functions.
|
||||
transfers []io.ReadCloser
|
||||
|
||||
// deferring is set if command execution and event delivery is deferred
|
||||
// to the next frame.
|
||||
deferring bool
|
||||
|
||||
// scratchFilter is for garbage-free construction of ephemeral filters.
|
||||
scratchFilter filter
|
||||
}
|
||||
@@ -109,7 +113,9 @@ type SemanticID uint
|
||||
type handler struct {
|
||||
// active tracks whether the handler was active in the current
|
||||
// frame. Router deletes state belonging to inactive handlers during Frame.
|
||||
active bool
|
||||
active bool
|
||||
// old is true iff the handler was aded in a previous frame.
|
||||
old bool
|
||||
pointer pointerHandler
|
||||
key keyHandler
|
||||
// filter the handler has asked for through event handling
|
||||
@@ -129,6 +135,8 @@ type filter struct {
|
||||
// stateChange represents the new state and outgoing events
|
||||
// resulting from an incoming event.
|
||||
type stateChange struct {
|
||||
// event, if set, is the trigger for the change.
|
||||
event event.Event
|
||||
state inputState
|
||||
events []taggedEvent
|
||||
}
|
||||
@@ -152,13 +160,12 @@ func (q *Router) Source() Source {
|
||||
return Source{r: q}
|
||||
}
|
||||
|
||||
// Queue a command to be executed after the current frame
|
||||
// has completed.
|
||||
func (s Source) Queue(c Command) {
|
||||
// Execute a command.
|
||||
func (s Source) Execute(c Command) {
|
||||
if !s.Enabled() {
|
||||
return
|
||||
}
|
||||
s.r.queue(c)
|
||||
s.r.execute(c)
|
||||
}
|
||||
|
||||
// Enabled reports whether the source is enabled. Only enabled
|
||||
@@ -195,6 +202,9 @@ func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event {
|
||||
}
|
||||
}
|
||||
h.nextFilter.Merge(q.scratchFilter)
|
||||
if q.deferring {
|
||||
return events
|
||||
}
|
||||
// Accumulate events from state changes until there are no more
|
||||
// matching events.
|
||||
matchedIdx := 0
|
||||
@@ -234,6 +244,20 @@ func (q *Router) collapseState(idx int) {
|
||||
// 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) {
|
||||
var remaining []event.Event
|
||||
if n := len(q.changes); n > 0 {
|
||||
if q.deferring {
|
||||
// Collect events for replay.
|
||||
for _, ch := range q.changes[1:] {
|
||||
remaining = append(remaining, ch.event)
|
||||
}
|
||||
q.changes = append(q.changes[:0], stateChange{state: q.changes[0].state})
|
||||
} else {
|
||||
// Collapse state.
|
||||
state := q.changes[n-1].state
|
||||
q.changes = append(q.changes[:0], stateChange{state: state})
|
||||
}
|
||||
}
|
||||
for _, rc := range q.transfers {
|
||||
if rc != nil {
|
||||
rc.Close()
|
||||
@@ -241,11 +265,7 @@ func (q *Router) Frame(frame *op.Ops) {
|
||||
}
|
||||
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})
|
||||
}
|
||||
q.deferring = false
|
||||
for _, h := range q.handlers {
|
||||
h.filter, h.nextFilter = h.nextFilter, h.filter
|
||||
h.nextFilter.Reset()
|
||||
@@ -263,12 +283,17 @@ func (q *Router) Frame(frame *op.Ops) {
|
||||
delete(q.handlers, k)
|
||||
} else {
|
||||
h.active = false
|
||||
h.old = true
|
||||
}
|
||||
}
|
||||
q.executeCommands()
|
||||
q.changePointerState(q.pointer.queue.Frame(q.handlers, q.lastState().pointerState))
|
||||
kstate := q.key.queue.Frame(q.handlers, q.lastState().keyState)
|
||||
q.changeKeyState(kstate, nil)
|
||||
q.Queue(remaining...)
|
||||
st := q.lastState()
|
||||
pst, evts := q.pointer.queue.Frame(q.handlers, st.pointerState)
|
||||
st.pointerState = pst
|
||||
st.keyState = q.key.queue.Frame(q.handlers, q.lastState().keyState)
|
||||
q.changeState(nil, st, evts)
|
||||
|
||||
// Collapse state and events.
|
||||
q.collapseState(len(q.changes) - 1)
|
||||
|
||||
@@ -324,9 +349,11 @@ 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(q.handlers, state.pointerState, e))
|
||||
pstate, evts := q.pointer.queue.Push(q.handlers, state.pointerState, e)
|
||||
state.pointerState = pstate
|
||||
return q.changeState(e, state, evts)
|
||||
case key.Event:
|
||||
return q.addEvents(q.queueKeyEvent(state.keyState, e))
|
||||
return q.changeState(e, state, q.queueKeyEvent(state.keyState, e))
|
||||
case key.SnippetEvent:
|
||||
// Expand existing, overlapping snippet.
|
||||
if r := state.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
|
||||
@@ -341,22 +368,58 @@ func (q *Router) processEvent(e event.Event) bool {
|
||||
if f := state.focus; f != nil {
|
||||
evts = append(evts, taggedEvent{tag: f, event: e})
|
||||
}
|
||||
return q.addEvents(evts)
|
||||
return q.changeState(e, state, 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)
|
||||
return q.changeState(e, state, evts)
|
||||
case transfer.DataEvent:
|
||||
return q.changeClipboardState(q.cqueue.Push(state.clipboardState, e))
|
||||
cstate, evts := q.cqueue.Push(state.clipboardState, e)
|
||||
state.clipboardState = cstate
|
||||
return q.changeState(e, state, evts)
|
||||
default:
|
||||
panic("unknown event type")
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Router) queue(f Command) {
|
||||
q.commands = append(q.commands, f)
|
||||
func (q *Router) execute(c Command) {
|
||||
// The command can be executed immediately if:
|
||||
//
|
||||
// - event delivery is not frozen, and
|
||||
// - the influencing tag and event receivers were all seen
|
||||
// in the previous frame, and
|
||||
// - no event receiver has completed their event handling.
|
||||
if !q.deferring {
|
||||
tag, ch := q.executeCommand(c)
|
||||
immediate := true
|
||||
if tag != nil {
|
||||
h, ok := q.handlers[tag]
|
||||
immediate = immediate && ok && h.old
|
||||
}
|
||||
for _, e := range ch.events {
|
||||
h, ok := q.handlers[e.tag]
|
||||
immediate = immediate && ok && h.old && !h.nextFilter.Matches(e.event)
|
||||
}
|
||||
if immediate {
|
||||
// Hold on to the remaining events for state replay.
|
||||
var evts []event.Event
|
||||
for _, ch := range q.changes {
|
||||
if ch.event != nil {
|
||||
evts = append(evts, ch.event)
|
||||
}
|
||||
}
|
||||
if len(q.changes) > 1 {
|
||||
q.changes = q.changes[:1]
|
||||
}
|
||||
q.changeState(nil, ch.state, ch.events)
|
||||
q.Queue(evts...)
|
||||
return
|
||||
}
|
||||
}
|
||||
q.deferring = true
|
||||
q.commands = append(q.commands, c)
|
||||
}
|
||||
|
||||
func (q *Router) state() inputState {
|
||||
@@ -373,58 +436,47 @@ func (q *Router) lastState() inputState {
|
||||
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:
|
||||
kstate := q.key.queue.setSelection(state.keyState, req)
|
||||
q.changeKeyState(kstate, nil)
|
||||
case key.FocusCmd:
|
||||
q.changeKeyState(q.key.queue.Focus(q.handlers, state.keyState, req.Tag))
|
||||
case key.SoftKeyboardCmd:
|
||||
kstate := state.keyState.softKeyboard(req.Show)
|
||||
q.changeKeyState(kstate, nil)
|
||||
case key.SnippetCmd:
|
||||
kstate := q.key.queue.setSnippet(state.keyState, req)
|
||||
q.changeKeyState(kstate, nil)
|
||||
case transfer.OfferCmd:
|
||||
q.changePointerState(q.pointer.queue.offerData(q.handlers, state.pointerState, req))
|
||||
case clipboard.WriteCmd:
|
||||
q.cqueue.ProcessWriteClipboard(req)
|
||||
case clipboard.ReadCmd:
|
||||
cstate := q.cqueue.ProcessReadClipboard(state.clipboardState, req.Tag)
|
||||
q.changeClipboardState(cstate, nil)
|
||||
case pointer.GrabCmd:
|
||||
q.changePointerState(q.pointer.queue.grab(state.pointerState, req))
|
||||
}
|
||||
for _, c := range q.commands {
|
||||
_, ch := q.executeCommand(c)
|
||||
q.changeState(nil, ch.state, ch.events)
|
||||
}
|
||||
q.commands = nil
|
||||
}
|
||||
|
||||
func (q *Router) addEvents(evts []taggedEvent) bool {
|
||||
return q.changeState(q.lastState(), evts)
|
||||
// executeCommand the command and return the resulting state change along with the
|
||||
// tag the state change depended on, if any.
|
||||
func (q *Router) executeCommand(c Command) (event.Tag, stateChange) {
|
||||
state := q.state()
|
||||
var evts []taggedEvent
|
||||
var tag event.Tag
|
||||
switch req := c.(type) {
|
||||
case key.SelectionCmd:
|
||||
tag = req.Tag
|
||||
state.keyState = q.key.queue.setSelection(state.keyState, req)
|
||||
case key.FocusCmd:
|
||||
tag = req.Tag
|
||||
state.keyState, evts = q.key.queue.Focus(q.handlers, state.keyState, req.Tag)
|
||||
case key.SoftKeyboardCmd:
|
||||
state.keyState = state.keyState.softKeyboard(req.Show)
|
||||
case key.SnippetCmd:
|
||||
tag = req.Tag
|
||||
state.keyState = q.key.queue.setSnippet(state.keyState, req)
|
||||
case transfer.OfferCmd:
|
||||
tag = req.Tag
|
||||
state.pointerState, evts = q.pointer.queue.offerData(q.handlers, state.pointerState, req)
|
||||
case clipboard.WriteCmd:
|
||||
q.cqueue.ProcessWriteClipboard(req)
|
||||
case clipboard.ReadCmd:
|
||||
state.clipboardState = q.cqueue.ProcessReadClipboard(state.clipboardState, req.Tag)
|
||||
case pointer.GrabCmd:
|
||||
tag = req.Tag
|
||||
state.pointerState, evts = q.pointer.queue.grab(state.pointerState, req)
|
||||
}
|
||||
return tag, stateChange{state: state, events: evts}
|
||||
}
|
||||
|
||||
func (q *Router) changeState(state inputState, evts []taggedEvent) bool {
|
||||
func (q *Router) changeState(e event.Event, state inputState, evts []taggedEvent) bool {
|
||||
// Wrap pointer.DataEvent.Open functions to detect them not being called.
|
||||
for i := range evts {
|
||||
e := &evts[i]
|
||||
@@ -439,16 +491,18 @@ func (q *Router) changeState(state inputState, evts []taggedEvent) bool {
|
||||
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})
|
||||
// Initialize the first change to contain the current state
|
||||
// and events that are bound for the current frame.
|
||||
if len(q.changes) == 0 {
|
||||
q.changes = append(q.changes, stateChange{})
|
||||
}
|
||||
if e != nil && len(evts) > 0 {
|
||||
// An event triggered events bound for user receivers. Add a state change to be
|
||||
// able to redo the change in case of a command execution.
|
||||
q.changes = append(q.changes, stateChange{event: e, state: state, events: evts})
|
||||
} else {
|
||||
// Otherwise, merge with previous change.
|
||||
prev := &q.changes[n-1]
|
||||
prev := &q.changes[len(q.changes)-1]
|
||||
prev.state = state
|
||||
prev.events = append(prev.events, evts...)
|
||||
}
|
||||
@@ -504,8 +558,10 @@ func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent {
|
||||
}
|
||||
|
||||
func (q *Router) MoveFocus(dir key.FocusDirection) bool {
|
||||
ks, evts := q.key.queue.MoveFocus(q.handlers, q.lastState().keyState, dir)
|
||||
return q.changeKeyState(ks, evts)
|
||||
state := q.lastState()
|
||||
kstate, evts := q.key.queue.MoveFocus(q.handlers, state.keyState, dir)
|
||||
state.keyState = kstate
|
||||
return q.changeState(nil, state, evts)
|
||||
}
|
||||
|
||||
// RevealFocus scrolls the current focus (if any) into viewport
|
||||
@@ -546,7 +602,7 @@ func (q *Router) ScrollFocus(dist image.Point) {
|
||||
}
|
||||
kh := &q.handlers[focus].key
|
||||
area := q.key.queue.AreaFor(kh)
|
||||
q.addEvents(q.pointer.queue.Deliver(q.handlers, area, pointer.Event{
|
||||
q.changeState(nil, q.lastState(), q.pointer.queue.Deliver(q.handlers, area, pointer.Event{
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Touch,
|
||||
Scroll: f32internal.FPt(dist),
|
||||
@@ -593,16 +649,19 @@ func (q *Router) ClickFocus() {
|
||||
}
|
||||
area := q.key.queue.AreaFor(kh)
|
||||
e.Kind = pointer.Press
|
||||
q.addEvents(q.pointer.queue.Deliver(q.handlers, area, e))
|
||||
state := q.lastState()
|
||||
q.changeState(nil, state, q.pointer.queue.Deliver(q.handlers, area, e))
|
||||
e.Kind = pointer.Release
|
||||
q.addEvents(q.pointer.queue.Deliver(q.handlers, area, e))
|
||||
q.changeState(nil, state, q.pointer.queue.Deliver(q.handlers, area, e))
|
||||
}
|
||||
|
||||
// TextInputState returns the input state from the most recent
|
||||
// call to Frame.
|
||||
func (q *Router) TextInputState() TextInputState {
|
||||
kstate, s := q.state().InputState()
|
||||
q.changeKeyState(kstate, nil)
|
||||
state := q.state()
|
||||
kstate, s := state.InputState()
|
||||
state.keyState = kstate
|
||||
q.changeState(nil, state, nil)
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -713,7 +772,7 @@ func (q *Router) collect() {
|
||||
pc.inputOp(tag, &s.pointer)
|
||||
a := pc.currentArea()
|
||||
b := pc.currentAreaBounds()
|
||||
if s.filter.focusable {
|
||||
if s.filter.key.focusable {
|
||||
kq.inputOp(tag, &s.key, t, a, b)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user