diff --git a/io/input/clipboard.go b/io/input/clipboard.go index 2defc67e..d7ef98d5 100644 --- a/io/input/clipboard.go +++ b/io/input/clipboard.go @@ -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) { diff --git a/io/input/key.go b/io/input/key.go index 98a0e26b..5acd9a05 100644 --- a/io/input/key.go +++ b/io/input/key.go @@ -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) { diff --git a/io/input/pointer.go b/io/input/pointer.go index 730a75c8..722e883e 100644 --- a/io/input/pointer.go +++ b/io/input/pointer.go @@ -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. diff --git a/io/input/router.go b/io/input/router.go index 1a9f36de..3d481ce0 100644 --- a/io/input/router.go +++ b/io/input/router.go @@ -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 {