io/input: merge event queues

Replace the per-event event queues with a single queue of events, each
marked with the target tag. This change is a prerequisite for lazy event
delivery.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-11-15 17:25:16 -06:00
parent 3ba5fc557c
commit 651094d692
4 changed files with 141 additions and 141 deletions
+3 -2
View File
@@ -38,11 +38,12 @@ func (q *clipboardQueue) ReadClipboard() bool {
return true
}
func (q *clipboardQueue) Push(e event.Event, events *handlerEvents) {
func (q *clipboardQueue) Push(evts []taggedEvent, e event.Event) []taggedEvent {
for r := range q.receivers {
events.Add(r, e)
evts = append(evts, taggedEvent{tag: r, event: e})
delete(q.receivers, r)
}
return evts
}
func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
+12 -14
View File
@@ -167,10 +167,10 @@ func (q *keyQueue) updateFocusLayout() {
}
}
// MoveFocus attempts to move the focus in the direction of dir, returning true if it succeeds.
func (q *keyQueue) MoveFocus(dir key.FocusDirection, events *handlerEvents) bool {
// MoveFocus attempts to move the focus in the direction of dir.
func (q *keyQueue) MoveFocus(evts []taggedEvent, dir key.FocusDirection) []taggedEvent {
if len(q.dirOrder) == 0 {
return false
return nil
}
order := 0
if q.focus != nil {
@@ -195,8 +195,7 @@ func (q *keyQueue) MoveFocus(dir key.FocusDirection, events *handlerEvents) bool
}
}
order = (order + len(q.order)) % len(q.order)
q.Focus(q.order[order], events)
return true
return q.Focus(evts, q.order[order])
case key.FocusRight, key.FocusLeft:
next := order
if q.focus != nil {
@@ -208,8 +207,7 @@ func (q *keyQueue) MoveFocus(dir key.FocusDirection, events *handlerEvents) bool
if 0 <= next && next < len(q.dirOrder) {
newFocus := q.dirOrder[next]
if newFocus.row == focus.row {
q.Focus(newFocus.tag, events)
return true
return q.Focus(evts, newFocus.tag)
}
}
case key.FocusUp, key.FocusDown:
@@ -245,11 +243,10 @@ func (q *keyQueue) MoveFocus(dir key.FocusDirection, events *handlerEvents) bool
order += delta
}
if closest != nil {
q.Focus(closest, events)
return true
return q.Focus(evts, closest)
}
}
return false
return nil
}
func (q *keyQueue) BoundsFor(t event.Tag) image.Rectangle {
@@ -284,26 +281,27 @@ func keyFilterMatch(f key.Filter, e key.Event) bool {
return true
}
func (q *keyQueue) Focus(focus event.Tag, events *handlerEvents) {
func (q *keyQueue) Focus(evts []taggedEvent, focus event.Tag) []taggedEvent {
if focus != nil {
if _, exists := q.handlers[focus]; !exists {
focus = nil
}
}
if focus == q.focus {
return
return evts
}
q.content = EditorState{}
if q.focus != nil {
events.Add(q.focus, key.FocusEvent{Focus: false})
evts = append(evts, taggedEvent{tag: q.focus, event: key.FocusEvent{Focus: false}})
}
q.focus = focus
if q.focus != nil {
events.Add(q.focus, key.FocusEvent{Focus: true})
evts = append(evts, taggedEvent{tag: q.focus, event: key.FocusEvent{Focus: true}})
}
if q.focus == nil || q.state == TextInputKeep {
q.state = TextInputClose
}
return evts
}
func (q *keyQueue) softKeyboard(show bool) {
+70 -50
View File
@@ -264,7 +264,7 @@ func (c *pointerCollector) actionInputOp(act system.Action) {
area.action = act
}
func (q *pointerQueue) grab(req pointer.GrabCmd, events *handlerEvents) {
func (q *pointerQueue) grab(evts []taggedEvent, req pointer.GrabCmd) []taggedEvent {
for _, p := range q.pointers {
if !p.pressed || p.id != req.ID {
continue
@@ -272,11 +272,16 @@ func (q *pointerQueue) grab(req pointer.GrabCmd, events *handlerEvents) {
// Drop other handlers that lost their grab.
for i := len(p.handlers) - 1; i >= 0; i-- {
if tag := p.handlers[i]; tag != req.Tag {
q.dropHandler(events, tag)
evts = append(evts, taggedEvent{
tag: tag,
event: pointer.Event{Kind: pointer.Cancel},
})
q.dropHandler(tag)
}
}
break
}
return evts
}
func (c *pointerCollector) inputOp(tag event.Tag) {
@@ -352,27 +357,29 @@ func (q *pointerQueue) targetFilter(tag event.Tag, f transfer.TargetFilter) {
h.targetMimes = append(h.targetMimes, f.Type)
}
func (q *pointerQueue) offerData(req transfer.OfferCmd, events *handlerEvents) {
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]
p := q.pointers[i]
if p.dataSource != req.Tag {
continue
}
defer q.deliverTransferCancelEvent(p, events)
if p.dataTarget == nil {
return
q.pointers[i], evts = q.deliverTransferCancelEvent(p, evts)
break
}
events.Add(p.dataTarget, transfer.DataEvent{
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)
break
}
return evts
}
func (c *pointerCollector) reset() {
@@ -596,10 +603,10 @@ func (q *pointerQueue) reset() {
q.transfers = nil
}
func (q *pointerQueue) Frame(events *handlerEvents) {
func (q *pointerQueue) Frame(evts []taggedEvent) []taggedEvent {
for k, h := range q.handlers {
if !h.active {
q.dropHandler(nil, k)
q.dropHandler(k)
delete(q.handlers, k)
continue
}
@@ -616,15 +623,13 @@ func (q *pointerQueue) Frame(events *handlerEvents) {
}
}
for i := range q.pointers {
p := &q.pointers[i]
q.deliverEnterLeaveEvents(p, events, p.last)
p := q.pointers[i]
q.pointers[i], evts = q.deliverEnterLeaveEvents(p, evts, p.last)
}
return evts
}
func (q *pointerQueue) dropHandler(events *handlerEvents, tag event.Tag) {
if events != nil {
events.Add(tag, pointer.Event{Kind: pointer.Cancel})
}
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-- {
@@ -652,7 +657,7 @@ func (q *pointerQueue) pointerOf(e pointer.Event) int {
}
// Deliver is like Push, but delivers an event to a particular area.
func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEvents) {
func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event) []taggedEvent {
var sx, sy = e.Scroll.X, e.Scroll.Y
idx := len(q.hitTree) - 1
// Locate first potential receiver.
@@ -663,6 +668,7 @@ func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEven
}
idx--
}
var evts []taggedEvent
for idx != -1 {
n := &q.hitTree[idx]
idx = n.next
@@ -683,11 +689,12 @@ func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEven
sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y)
}
e.Position = q.invTransform(h.area, e.Position)
events.Add(n.tag, e)
evts = append(evts, taggedEvent{tag: n.tag, event: e})
if e.Kind != pointer.Scroll {
break
}
}
return evts
}
// SemanticArea returns the sematic content for area, and its parent area.
@@ -703,51 +710,60 @@ func (q *pointerQueue) SemanticArea(areaIdx int) (semanticContent, int) {
return semanticContent{}, -1
}
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
func (q *pointerQueue) Push(evts []taggedEvent, e pointer.Event) []taggedEvent {
if e.Kind == pointer.Cancel {
for k := range q.handlers {
evts = append(evts, taggedEvent{
event: pointer.Event{Kind: pointer.Cancel},
tag: k,
})
}
q.pointers = q.pointers[:0]
for k := range q.handlers {
q.dropHandler(events, k)
q.dropHandler(k)
}
return
return evts
}
pidx := q.pointerOf(e)
p := &q.pointers[pidx]
p.last = e
p := q.pointers[pidx]
switch e.Kind {
case pointer.Press:
q.deliverEnterLeaveEvents(p, events, e)
p, evts = q.deliverEnterLeaveEvents(p, evts, e)
p.pressed = true
q.deliverEvent(p, events, e)
evts = q.deliverEvent(p, evts, e)
case pointer.Move:
if p.pressed {
e.Kind = pointer.Drag
}
q.deliverEnterLeaveEvents(p, events, e)
q.deliverEvent(p, events, e)
p, evts = q.deliverEnterLeaveEvents(p, evts, e)
evts = q.deliverEvent(p, evts, e)
if p.pressed {
q.deliverDragEvent(p, events)
p, evts = q.deliverDragEvent(p, evts)
}
case pointer.Release:
q.deliverEvent(p, events, e)
evts = q.deliverEvent(p, evts, e)
p.pressed = false
q.deliverEnterLeaveEvents(p, events, e)
q.deliverDropEvent(p, events)
p, evts = q.deliverEnterLeaveEvents(p, evts, e)
p, evts = q.deliverDropEvent(p, evts)
case pointer.Scroll:
q.deliverEnterLeaveEvents(p, events, e)
q.deliverEvent(p, events, e)
p, evts = q.deliverEnterLeaveEvents(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:]...)
}
return evts
}
func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
func (q *pointerQueue) deliverEvent(p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent {
foremost := true
if p.pressed && len(p.handlers) == 1 {
e.Priority = pointer.Grabbed
@@ -758,7 +774,7 @@ func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e poi
h := q.handlers[k]
if e.Kind == pointer.Scroll {
if sx == 0 && sy == 0 {
return
return evts
}
// Distribute the scroll to the handler based on its ScrollRange.
sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X)
@@ -773,11 +789,12 @@ func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e poi
e.Priority = pointer.Foremost
}
e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e)
evts = append(evts, taggedEvent{event: e, tag: k})
}
return evts
}
func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
func (q *pointerQueue) deliverEnterLeaveEvents(p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent) {
var hits []event.Tag
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
// Consider non-mouse pointers leaving when they're released.
@@ -816,7 +833,7 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEv
if e.Kind&h.types != 0 {
e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e)
evts = append(evts, taggedEvent{tag: k, event: e})
}
}
// Deliver Enter events.
@@ -830,15 +847,16 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEv
if e.Kind&h.types != 0 {
e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e)
evts = append(evts, taggedEvent{tag: k, event: e})
}
}
p.entered = hits
return p, evts
}
func (q *pointerQueue) deliverDragEvent(p *pointerInfo, events *handlerEvents) {
func (q *pointerQueue) deliverDragEvent(p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
if p.dataSource != nil {
return
return p, evts
}
// Identify the data source.
for _, k := range p.entered {
@@ -851,16 +869,17 @@ func (q *pointerQueue) deliverDragEvent(p *pointerInfo, events *handlerEvents) {
// Notify all potential targets.
for k, tgt := range q.handlers {
if _, ok := firstMimeMatch(src, tgt); ok {
events.Add(k, transfer.InitiateEvent{})
evts = append(evts, taggedEvent{tag: k, event: transfer.InitiateEvent{}})
}
}
break
}
return p, evts
}
func (q *pointerQueue) deliverDropEvent(p *pointerInfo, events *handlerEvents) {
func (q *pointerQueue) deliverDropEvent(p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
if p.dataSource == nil {
return
return p, evts
}
// Request data from the source.
src := q.handlers[p.dataSource]
@@ -868,25 +887,26 @@ func (q *pointerQueue) deliverDropEvent(p *pointerInfo, events *handlerEvents) {
h := q.handlers[k]
if m, ok := firstMimeMatch(src, h); ok {
p.dataTarget = k
events.Add(p.dataSource, transfer.RequestEvent{Type: m})
return
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.RequestEvent{Type: m}})
return p, evts
}
}
// No valid target found, abort.
q.deliverTransferCancelEvent(p, events)
return q.deliverTransferCancelEvent(p, evts)
}
func (q *pointerQueue) deliverTransferCancelEvent(p *pointerInfo, events *handlerEvents) {
events.Add(p.dataSource, transfer.CancelEvent{})
func (q *pointerQueue) deliverTransferCancelEvent(p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.CancelEvent{}})
// Cancel all potential targets.
src := q.handlers[p.dataSource]
for k, h := range q.handlers {
if _, ok := firstMimeMatch(src, h); ok {
events.Add(k, transfer.CancelEvent{})
evts = append(evts, taggedEvent{tag: k, event: transfer.CancelEvent{}})
}
}
p.dataSource = nil
p.dataTarget = nil
return p, evts
}
// ClipFor clips r to the parents of area.
+56 -75
View File
@@ -35,7 +35,7 @@ type Router struct {
}
cqueue clipboardQueue
handlers handlerEvents
events []taggedEvent
reader ops.Reader
@@ -94,9 +94,10 @@ const (
// By convention, the zero value denotes the non-existent ID.
type SemanticID uint
type handlerEvents struct {
handlers map[event.Tag][]event.Event
hadEvents bool
// taggedEvent represents an event and its target handler.
type taggedEvent struct {
event event.Event
tag event.Tag
}
// Source returns a Source backed by this Router.
@@ -129,7 +130,7 @@ 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 resetEvents []event.Event
var evts []event.Event
for _, f := range filters {
switch f := f.(type) {
case key.Filter:
@@ -137,12 +138,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 {
resetEvents = append(resetEvents, reset)
evts = append(evts, reset)
}
case pointer.Filter:
q.pointer.queue.filterTag(k, f)
if reset, ok := q.pointer.queue.ResetEvent(k); ok {
resetEvents = append(resetEvents, reset)
evts = append(evts, reset)
}
case transfer.SourceFilter:
q.pointer.queue.sourceFilter(k, f)
@@ -150,15 +151,26 @@ func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event {
q.pointer.queue.targetFilter(k, f)
}
}
events := q.handlers.Events(k, filters...)
return append(resetEvents, events...)
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)
}
} else {
i++
}
}
return evts
}
// 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.handlers.Clear()
q.events = q.events[:0]
q.wakeup = false
var ops *ops.Ops
if frame != nil {
@@ -166,24 +178,26 @@ func (q *Router) Frame(frame *op.Ops) {
}
q.reader.Reset(ops)
q.collect()
q.executeCommands()
q.pointer.queue.Frame(&q.handlers)
evts := q.executeCommands(nil)
evts = q.pointer.queue.Frame(evts)
q.key.queue.Frame()
q.addEvents(evts)
if q.handlers.HadEvents() {
if len(evts) > 0 {
q.wakeup = true
q.wakeupTime = time.Time{}
}
}
// Queue events and report whether at least one handler had an event queued.
// Queue events and report whether at least one event matched a handler.
func (q *Router) Queue(events ...event.Event) bool {
var evts []taggedEvent
for _, e := range events {
switch e := e.(type) {
case pointer.Event:
q.pointer.queue.Push(e, &q.handlers)
evts = q.pointer.queue.Push(evts, e)
case key.Event:
q.queueKeyEvent(e)
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)) {
@@ -195,45 +209,51 @@ func (q *Router) Queue(events ...event.Event) bool {
}
}
if f := q.key.queue.focus; f != nil {
q.handlers.Add(f, e)
evts = append(evts, taggedEvent{tag: f, event: e})
}
case key.EditEvent, key.FocusEvent, key.SelectionEvent:
if f := q.key.queue.focus; f != nil {
q.handlers.Add(f, e)
evts = append(evts, taggedEvent{tag: f, event: e})
}
case transfer.DataEvent:
q.cqueue.Push(e, &q.handlers)
evts = q.cqueue.Push(evts, e)
}
}
return q.handlers.HadEvents()
q.addEvents(evts)
return len(evts) > 0
}
func (q *Router) queue(f Command) {
q.commands = append(q.commands, f)
}
func (q *Router) executeCommands() {
func (q *Router) executeCommands(evts []taggedEvent) []taggedEvent {
for _, req := range q.commands {
switch req := req.(type) {
case key.SelectionCmd:
q.key.queue.setSelection(req)
case key.FocusCmd:
q.key.queue.Focus(req.Tag, &q.handlers)
evts = q.key.queue.Focus(evts, req.Tag)
case key.SoftKeyboardCmd:
q.key.queue.softKeyboard(req.Show)
case key.SnippetCmd:
q.key.queue.setSnippet(req)
case transfer.OfferCmd:
q.pointer.queue.offerData(req, &q.handlers)
evts = q.pointer.queue.offerData(evts, req)
case clipboard.WriteCmd:
q.cqueue.ProcessWriteClipboard(req)
case clipboard.ReadCmd:
q.cqueue.ProcessReadClipboard(req.Tag)
case pointer.GrabCmd:
q.pointer.queue.grab(req, &q.handlers)
evts = q.pointer.queue.grab(evts, req)
}
}
q.commands = nil
return evts
}
func (q *Router) addEvents(evts []taggedEvent) {
q.events = append(q.events, evts...)
}
func rangeOverlaps(r1, r2 key.Range) bool {
@@ -250,12 +270,12 @@ func rangeNorm(r key.Range) key.Range {
return r
}
func (q *Router) queueKeyEvent(e key.Event) {
func (q *Router) queueKeyEvent(evts []taggedEvent, e key.Event) []taggedEvent {
kq := &q.key.queue
f := q.key.queue.focus
if f != nil && kq.Accepts(f, e) {
q.handlers.Add(f, e)
return
evts = append(evts, taggedEvent{tag: f, event: e})
return evts
}
pq := &q.pointer.queue
idx := len(pq.hitTree) - 1
@@ -277,14 +297,17 @@ func (q *Router) queueKeyEvent(e key.Event) {
continue
}
if kq.Accepts(n.tag, e) {
q.handlers.Add(n.tag, e)
evts = append(evts, taggedEvent{tag: n.tag, event: e})
break
}
}
return evts
}
func (q *Router) MoveFocus(dir key.FocusDirection) bool {
return q.key.queue.MoveFocus(dir, &q.handlers)
evts := q.key.queue.MoveFocus(nil, dir)
q.addEvents(evts)
return len(evts) > 0
}
// RevealFocus scrolls the current focus (if any) into viewport
@@ -321,11 +344,11 @@ func (q *Router) ScrollFocus(dist image.Point) {
return
}
area := q.key.queue.AreaFor(focus)
q.pointer.queue.Deliver(area, pointer.Event{
q.addEvents(q.pointer.queue.Deliver(area, pointer.Event{
Kind: pointer.Scroll,
Source: pointer.Touch,
Scroll: f32internal.FPt(dist),
}, &q.handlers)
}))
}
func max(p1, p2 image.Point) image.Point {
@@ -367,9 +390,9 @@ func (q *Router) ClickFocus() {
}
area := q.key.queue.AreaFor(focus)
e.Kind = pointer.Press
q.pointer.queue.Deliver(area, e, &q.handlers)
q.addEvents(q.pointer.queue.Deliver(area, e))
e.Kind = pointer.Release
q.pointer.queue.Deliver(area, e, &q.handlers)
q.addEvents(q.pointer.queue.Deliver(area, e))
}
// TextInputState returns the input state from the most recent
@@ -523,42 +546,6 @@ func (q *Router) WakeupTime() (time.Time, bool) {
return q.wakeupTime, q.wakeup
}
func (h *handlerEvents) init() {
if h.handlers == nil {
h.handlers = make(map[event.Tag][]event.Event)
}
}
func (h *handlerEvents) Add(k event.Tag, e event.Event) {
h.init()
h.handlers[k] = append(h.handlers[k], e)
h.hadEvents = true
}
func (h *handlerEvents) HadEvents() bool {
u := h.hadEvents
h.hadEvents = false
return u
}
func (h *handlerEvents) Events(k event.Tag, filters ...event.Filter) []event.Event {
var filtered []event.Event
if events, ok := h.handlers[k]; ok {
i := 0
for i < len(events) {
e := events[i]
if filtersMatches(filters, e) {
filtered = append(filtered, e)
events = append(events[:i], events[i+1:]...)
} else {
i++
}
}
h.handlers[k] = events
}
return filtered
}
func filtersMatches(filters []event.Filter, e event.Event) bool {
switch e := e.(type) {
case key.Event:
@@ -604,12 +591,6 @@ func filtersMatches(filters []event.Filter, e event.Event) bool {
return false
}
func (h *handlerEvents) Clear() {
for k := range h.handlers {
delete(h.handlers, k)
}
}
func decodeInvalidateOp(d []byte) op.InvalidateOp {
bo := binary.LittleEndian
if ops.OpType(d[0]) != ops.TypeInvalidate {