mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
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:
+24
-19
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user