diff --git a/gesture/gesture.go b/gesture/gesture.go index b43779c3..76a0ac36 100644 --- a/gesture/gesture.go +++ b/gesture/gesture.go @@ -315,7 +315,7 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, if e.Priority < pointer.Grabbed { slop := cfg.Dp(touchSlop) if dist := dist; dist >= slop || -slop >= dist { - q.Queue(pointer.GrabCmd{Tag: s, ID: e.PointerID}) + q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID}) } } else { s.last = v @@ -389,7 +389,7 @@ func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) []pointer.Even diff := e.Position.Sub(d.start) slop := cfg.Dp(touchSlop) if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) { - q.Queue(pointer.GrabCmd{Tag: d, ID: e.PointerID}) + q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID}) } } case pointer.Release, pointer.Cancel: diff --git a/io/input/clipboard_test.go b/io/input/clipboard_test.go index 400ccec2..f0020b8b 100644 --- a/io/input/clipboard_test.go +++ b/io/input/clipboard_test.go @@ -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) { diff --git a/io/input/key_test.go b/io/input/key_test.go index 3cfcd9ca..44ac4043 100644 --- a/io/input/key_test.go +++ b/io/input/key_test.go @@ -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) diff --git a/io/input/pointer_test.go b/io/input/pointer_test.go index dac2a495..f19d7d74 100644 --- a/io/input/pointer_test.go +++ b/io/input/pointer_test.go @@ -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. diff --git a/io/input/router.go b/io/input/router.go index 6982a89d..90f52a7f 100644 --- a/io/input/router.go +++ b/io/input/router.go @@ -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) } diff --git a/widget/button.go b/widget/button.go index e4141083..6eda1f42 100644 --- a/widget/button.go +++ b/widget/button.go @@ -80,7 +80,7 @@ func (b *Clickable) Pressed() bool { // Focus requests the input focus for the element. func (b *Clickable) Focus(gtx layout.Context) { - gtx.Queue(key.FocusCmd{Tag: &b.keyTag}) + gtx.Execute(key.FocusCmd{Tag: &b.keyTag}) } // Focused reports whether b has focus. @@ -149,7 +149,7 @@ func (b *Clickable) Update(gtx layout.Context) []Click { } case gesture.KindPress: if e.Source == pointer.Mouse { - gtx.Queue(key.FocusCmd{Tag: &b.keyTag}) + gtx.Execute(key.FocusCmd{Tag: &b.keyTag}) } b.history = append(b.history, Press{ Position: e.Position, diff --git a/widget/dnd.go b/widget/dnd.go index bac8a6ab..1dd1be9b 100644 --- a/widget/dnd.go +++ b/widget/dnd.go @@ -76,7 +76,7 @@ func (d *Draggable) Update(gtx layout.Context) (mime string, requested bool) { // Offer the data ready for a drop. Must be called after being Requested. // The mime must be one in the requested list. func (d *Draggable) Offer(gtx layout.Context, mime string, data io.ReadCloser) { - gtx.Queue(transfer.OfferCmd{Tag: &d.handle, Type: mime, Data: data}) + gtx.Execute(transfer.OfferCmd{Tag: &d.handle, Type: mime, Data: data}) } // Pos returns the drag position relative to its initial click position. diff --git a/widget/dnd_test.go b/widget/dnd_test.go index af915388..8165f7a7 100644 --- a/widget/dnd_test.go +++ b/widget/dnd_test.go @@ -51,17 +51,16 @@ func TestDraggable(t *testing.T) { }, ) ofr := &offer{data: "hello"} - drag.Offer(gtx, "file", ofr) drag.Update(gtx) r.Events(drag, transfer.TargetFilter{Type: drag.Type}) + drag.Offer(gtx, "file", ofr) r.Frame(gtx.Ops) evs := r.Events(drag, transfer.TargetFilter{Type: drag.Type}) if len(evs) != 2 { - t.Fatalf("expected 2 event, got %d", len(evs)) + t.Fatalf("expected 3 event, got %d", len(evs)) } ev := evs[0].(transfer.DataEvent) - ev.Open = nil if got, want := ev.Type, "file"; got != want { t.Errorf("expected %v; got %v", got, want) } diff --git a/widget/editor.go b/widget/editor.go index 0b917d3d..08678f96 100644 --- a/widget/editor.go +++ b/widget/editor.go @@ -475,13 +475,13 @@ func (e *Editor) command(gtx layout.Context, k key.Event) { // half is in Editor.processKey() under clipboard.Event. case "V": if !e.ReadOnly { - gtx.Queue(clipboard.ReadCmd{Tag: &e.eventKey}) + gtx.Execute(clipboard.ReadCmd{Tag: &e.eventKey}) } // Copy or Cut selection -- ignored if nothing selected. case "C", "X": e.scratch = e.text.SelectedText(e.scratch) if text := string(e.scratch); text != "" { - gtx.Queue(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(strings.NewReader(text))}) + gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(strings.NewReader(text))}) if k.Name == "X" && !e.ReadOnly { e.Delete(1) } @@ -556,8 +556,8 @@ func (e *Editor) command(gtx layout.Context, k key.Event) { // Focus requests the input focus for the Editor. func (e *Editor) Focus(gtx layout.Context) { - gtx.Queue(key.FocusCmd{Tag: &e.eventKey}) - gtx.Queue(key.SoftKeyboardCmd{Show: true}) + gtx.Execute(key.FocusCmd{Tag: &e.eventKey}) + gtx.Execute(key.SoftKeyboardCmd{Show: true}) } // Focused returns whether the editor is focused or not. @@ -601,7 +601,7 @@ func (e *Editor) Update(gtx layout.Context) { } if newSel != e.ime.selection { e.ime.selection = newSel - gtx.Queue(key.SelectionCmd{Tag: &e.eventKey, Range: newSel.rng, Caret: newSel.caret}) + gtx.Execute(key.SelectionCmd{Tag: &e.eventKey, Range: newSel.rng, Caret: newSel.caret}) } e.updateSnippet(gtx, e.ime.start, e.ime.end) @@ -658,7 +658,7 @@ func (e *Editor) updateSnippet(gtx layout.Context, start, end int) { return } e.ime.snippet = newSnip - gtx.Queue(key.SnippetCmd{Tag: &e.eventKey, Snippet: newSnip}) + gtx.Execute(key.SnippetCmd{Tag: &e.eventKey, Snippet: newSnip}) } func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.CallOp) layout.Dimensions { diff --git a/widget/enum.go b/widget/enum.go index 597356fa..1326f1bc 100644 --- a/widget/enum.go +++ b/widget/enum.go @@ -51,7 +51,7 @@ func (e *Enum) Update(gtx layout.Context) bool { switch ev.Kind { case gesture.KindPress: if ev.Source == pointer.Mouse { - gtx.Queue(key.FocusCmd{Tag: &state.tag}) + gtx.Execute(key.FocusCmd{Tag: &state.tag}) } case gesture.KindClick: if state.key != e.Value { diff --git a/widget/selectable.go b/widget/selectable.go index 11e9bb5f..28fbc44b 100644 --- a/widget/selectable.go +++ b/widget/selectable.go @@ -100,8 +100,8 @@ func (l *Selectable) initialize() { // Focus requests the input focus for the label. func (l *Selectable) Focus(gtx layout.Context) { - gtx.Queue(key.FocusCmd{Tag: l}) - gtx.Queue(key.SoftKeyboardCmd{Show: true}) + gtx.Execute(key.FocusCmd{Tag: l}) + gtx.Execute(key.SoftKeyboardCmd{Show: true}) } // Focused returns whether the label is focused or not. @@ -348,7 +348,7 @@ func (e *Selectable) command(gtx layout.Context, k key.Event) { case "C", "X": e.scratch = e.text.SelectedText(e.scratch) if text := string(e.scratch); text != "" { - gtx.Queue(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(strings.NewReader(text))}) + gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(strings.NewReader(text))}) } // Select all case "A":