From 67b58a60062099bb5ba743a208cb3854d9405ebc Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 23 Nov 2023 17:37:29 -0600 Subject: [PATCH] io/input: merge pointer and key filters Refactor the pointer and key filter unions into the handler state struct. This is a preparation for replacing calls to filtersMatches with queries to the filter union. Signed-off-by: Elias Naur --- io/input/key.go | 49 +++++++------- io/input/pointer.go | 157 +++++++++++++++++++++++++++----------------- io/input/router.go | 113 ++++++++++++++++--------------- 3 files changed, 178 insertions(+), 141 deletions(-) diff --git a/io/input/key.go b/io/input/key.go index 4d979c71..48cef1cf 100644 --- a/io/input/key.go +++ b/io/input/key.go @@ -46,12 +46,7 @@ type keyHandler struct { hint key.InputHint orderPlusOne int dirOrder int - // filter are the key filters accumulated in the previous frame, - // used for routing events in the current frame. - filter keyFilter - // nextFilter is the filter accumulator for the current frame. - nextFilter keyFilter - trans f32.Affine2D + trans f32.Affine2D } type keyFilter struct { @@ -97,8 +92,6 @@ func (q *keyQueue) InputHint(handlers map[event.Tag]*handler, state keyState) (k } func (k *keyHandler) Reset() { - k.filter, k.nextFilter = k.nextFilter, k.filter - k.nextFilter = keyFilter{} k.visible = false k.orderPlusOne = 0 k.hint = key.HintAny @@ -119,7 +112,7 @@ func (k *keyHandler) ResetEvent() (event.Event, bool) { func (q *keyQueue) Frame(handlers map[event.Tag]*handler, state keyState) keyState { if state.focus != nil { - if h, ok := handlers[state.focus]; !ok || !h.key.isFocusable() { + if h, ok := handlers[state.focus]; !ok || !h.filter.key.focusable || !h.key.visible { // Remove focus from the handler that is no longer focusable. state.focus = nil state.state = TextInputClose @@ -260,11 +253,16 @@ func (q *keyQueue) AreaFor(k *keyHandler) int { return q.dirOrder[order].area } -func (k *keyHandler) Accepts(e key.Event) bool { - for _, f := range k.filter.filters { - if keyFilterMatch(f, e) { - return true +func (k *keyFilter) Matches(e event.Event) bool { + switch e := e.(type) { + case key.Event: + for _, f := range k.filters { + if keyFilterMatch(f, e) { + return true + } } + case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent: + return k.focusable } return false } @@ -284,7 +282,7 @@ func keyFilterMatch(f key.Filter, e key.Event) bool { func (q *keyQueue) Focus(handlers map[event.Tag]*handler, state keyState, focus event.Tag) (keyState, []taggedEvent) { if focus != nil { - if h, exists := handlers[focus]; !exists || !h.key.isFocusable() { + if h, exists := handlers[focus]; !exists || !h.filter.key.focusable || !h.key.visible { focus = nil } } @@ -315,16 +313,23 @@ func (s keyState) softKeyboard(show bool) keyState { return s } -func (k *keyHandler) Filter(f key.Filter) { - k.nextFilter.filters = append(k.nextFilter.filters, f) +func (k *keyFilter) Add(f event.Filter) { + switch f := f.(type) { + case key.FocusFilter: + k.focusable = true + case key.Filter: + for _, f2 := range k.filters { + if f == f2 { + return + } + } + k.filters = append(k.filters, f) + } } -func (k *keyHandler) isFocusable() bool { - return k.filter.focusable && k.visible -} - -func (k *keyHandler) Focusable() { - k.nextFilter.focusable = true +func (k *keyFilter) Merge(k2 keyFilter) { + k.focusable = k.focusable || k2.focusable + k.filters = append(k.filters, k2.filters...) } func (q *keyQueue) inputOp(tag event.Tag, state *keyHandler, t f32.Affine2D, area int, bounds image.Rectangle) { diff --git a/io/input/pointer.go b/io/input/pointer.go index cf56e899..cc90f3f8 100644 --- a/io/input/pointer.go +++ b/io/input/pointer.go @@ -66,12 +66,6 @@ type pointerHandler struct { // setup tracks whether the handler has received // the pointer.Cancel event that resets its state. setup bool - // filter is the combined filter of every filter the handler has - // asked for through event handling in the previous frame. It is - // used for routing events in the current frame. - filter pointerFilter - // prevFilter is the filter being built in the current frame. - nextFilter pointerFilter } // pointerFilter represents the union of a set of pointer filters. @@ -249,8 +243,6 @@ func (c *pointerCollector) newHandler(tag event.Tag, state *pointerHandler) { func (s *pointerHandler) Reset() { s.areaPlusOne = 0 - s.filter = s.nextFilter - s.nextFilter = pointerFilter{} } func (c *pointerCollector) actionInputOp(act system.Action) { @@ -287,9 +279,73 @@ func (c *pointerCollector) inputOp(tag event.Tag, state *pointerHandler) { c.newHandler(tag, state) } -func (s *pointerHandler) Filter(tag event.Tag, f pointer.Filter) { - s.nextFilter.kinds = s.nextFilter.kinds | f.Kinds - s.nextFilter.scrollRange = s.nextFilter.scrollRange.Union(f.ScrollBounds) +func (p *pointerFilter) Add(f event.Filter) { + switch f := f.(type) { + case transfer.SourceFilter: + for _, m := range p.sourceMimes { + if m == f.Type { + return + } + } + p.sourceMimes = append(p.sourceMimes, f.Type) + case transfer.TargetFilter: + for _, m := range p.targetMimes { + if m == f.Type { + return + } + } + p.targetMimes = append(p.targetMimes, f.Type) + case pointer.Filter: + p.kinds = p.kinds | f.Kinds + p.scrollRange = p.scrollRange.Union(f.ScrollBounds) + } +} + +func (p *pointerFilter) Matches(e event.Event) bool { + switch e := e.(type) { + case pointer.Event: + return e.Kind&p.kinds == e.Kind + case transfer.CancelEvent, transfer.InitiateEvent: + return len(p.sourceMimes) > 0 || len(p.targetMimes) > 0 + case transfer.RequestEvent: + for _, t := range p.sourceMimes { + if t == e.Type { + return true + } + } + case transfer.DataEvent: + for _, t := range p.targetMimes { + if t == e.Type { + return true + } + } + } + return false +} + +func (p *pointerFilter) Merge(p2 pointerFilter) { + p.kinds = p.kinds | p2.kinds + p.scrollRange = p.scrollRange.Union(p2.scrollRange) + p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...) + p.targetMimes = append(p.targetMimes, p2.targetMimes...) +} + +// clampScroll splits a scroll distance in the remaining scroll and the +// scroll accepted by the filter. +func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) { + left.X, scrolled.X = clampSplit(scroll.X, p.scrollRange.Min.X, p.scrollRange.Max.X) + left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollRange.Min.Y, p.scrollRange.Max.Y) + return +} + +func clampSplit(v float32, min, max int) (float32, float32) { + if m := float32(max); v > m { + return v - m, m + } + if m := float32(min); v < m { + return v - m, m + } + return 0, v } func (s *pointerHandler) ResetEvent() (event.Event, bool) { @@ -341,14 +397,6 @@ func (c *pointerCollector) cursor(cursor pointer.Cursor) { area.cursor = cursor } -func (s *pointerHandler) SourceFilter(tag event.Tag, f transfer.SourceFilter) { - s.nextFilter.sourceMimes = append(s.nextFilter.sourceMimes, f.Type) -} - -func (s *pointerHandler) TargetFilter(tag event.Tag, f transfer.TargetFilter) { - s.nextFilter.targetMimes = append(s.nextFilter.targetMimes, f.Type) -} - func (q *pointerQueue) offerData(handlers map[event.Tag]*handler, state pointerState, req transfer.OfferCmd) (pointerState, []taggedEvent) { var evts []taggedEvent for i, p := range state.pointers { @@ -572,10 +620,10 @@ func (q *pointerQueue) Frame(handlers map[event.Tag]*handler, state pointerState for _, h := range handlers { if h.pointer.areaPlusOne != 0 { area := &q.areas[h.pointer.areaPlusOne-1] - if h.pointer.filter.kinds&(pointer.Press|pointer.Release) != 0 { + if h.filter.pointer.kinds&(pointer.Press|pointer.Release) != 0 { area.semantic.content.gestures |= ClickGesture } - if h.pointer.filter.kinds&pointer.Scroll != 0 { + if h.filter.pointer.kinds&pointer.Scroll != 0 { area.semantic.content.gestures |= ScrollGesture } area.semantic.valid = area.semantic.content.gestures != 0 @@ -630,7 +678,7 @@ func (s pointerState) pointerOf(e pointer.Event) (pointerState, int) { // Deliver is like Push, but delivers an event to a particular area. func (q *pointerQueue) Deliver(handlers map[event.Tag]*handler, areaIdx int, e pointer.Event) []taggedEvent { - var sx, sy = e.Scroll.X, e.Scroll.Y + scroll := e.Scroll idx := len(q.hitTree) - 1 // Locate first potential receiver. for idx != -1 { @@ -645,18 +693,15 @@ func (q *pointerQueue) Deliver(handlers map[event.Tag]*handler, areaIdx int, e p n := &q.hitTree[idx] idx = n.next h, ok := handlers[n.tag] - if !ok || e.Kind&h.pointer.filter.kinds == 0 { + if !ok || !h.filter.pointer.Matches(e) { continue } - f := h.pointer.filter e := e if e.Kind == pointer.Scroll { - if sx == 0 && sy == 0 { + if scroll == (f32.Point{}) { break } - // Distribute the scroll to the handler based on its ScrollRange. - sx, e.Scroll.X = setScrollEvent(sx, f.scrollRange.Min.X, f.scrollRange.Max.X) - sy, e.Scroll.Y = setScrollEvent(sy, f.scrollRange.Min.Y, f.scrollRange.Max.Y) + scroll, e.Scroll = h.filter.pointer.clampScroll(scroll) } e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position) evts = append(evts, taggedEvent{tag: n.tag, event: e}) @@ -739,23 +784,21 @@ func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerIn e.Priority = pointer.Grabbed foremost = false } - var sx, sy = e.Scroll.X, e.Scroll.Y + scroll := e.Scroll for _, k := range p.handlers { h, ok := handlers[k] if !ok { continue } - f := h.pointer.filter + f := h.filter.pointer + if !f.Matches(e) { + continue + } if e.Kind == pointer.Scroll { - if sx == 0 && sy == 0 { + if scroll == (f32.Point{}) { return evts } - // Distribute the scroll to the handler based on its ScrollRange. - sx, e.Scroll.X = setScrollEvent(sx, f.scrollRange.Min.X, f.scrollRange.Max.X) - sy, e.Scroll.Y = setScrollEvent(sy, f.scrollRange.Min.Y, f.scrollRange.Max.Y) - } - if e.Kind&f.kinds == 0 { - continue + scroll, e.Scroll = f.clampScroll(scroll) } e := e if foremost { @@ -774,9 +817,9 @@ func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press { // Consider non-mouse pointers leaving when they're released. } else { - var transSrc *pointerHandler + var transSrc *pointerFilter if p.dataSource != nil { - transSrc = &handlers[p.dataSource].pointer + transSrc = &handlers[p.dataSource].filter.pointer } cursor = q.hitTest(e.Position, func(n *hitNode) bool { h, ok := handlers[n.tag] @@ -792,7 +835,7 @@ func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, add = true } if transSrc != nil { - if _, ok := firstMimeMatch(transSrc, &h.pointer); ok { + if _, ok := firstMimeMatch(transSrc, &h.filter.pointer); ok { add = true } } @@ -820,7 +863,7 @@ func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, e := e e.Kind = pointer.Leave - if e.Kind&h.pointer.filter.kinds != 0 { + if h.filter.pointer.Matches(e) { e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position) evts = append(evts, taggedEvent{tag: k, event: e}) } @@ -838,7 +881,7 @@ func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, e := e e.Kind = pointer.Enter - if e.Kind&h.pointer.filter.kinds != 0 { + if h.filter.pointer.Matches(e) { e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position) evts = append(evts, taggedEvent{tag: k, event: e}) } @@ -853,15 +896,15 @@ func (q *pointerQueue) deliverDragEvent(handlers map[event.Tag]*handler, p point } // Identify the data source. for _, k := range p.entered { - src := &handlers[k].pointer - if len(src.filter.sourceMimes) == 0 { + src := &handlers[k].filter.pointer + if len(src.sourceMimes) == 0 { continue } // One data source handler per pointer. p.dataSource = k // Notify all potential targets. for k, tgt := range handlers { - if _, ok := firstMimeMatch(src, &tgt.pointer); ok { + if _, ok := firstMimeMatch(src, &tgt.filter.pointer); ok { evts = append(evts, taggedEvent{tag: k, event: transfer.InitiateEvent{}}) } } @@ -875,10 +918,10 @@ func (q *pointerQueue) deliverDropEvent(handlers map[event.Tag]*handler, p point return p, evts } // Request data from the source. - src := &handlers[p.dataSource].pointer + src := &handlers[p.dataSource].filter.pointer for _, k := range p.entered { h := handlers[k] - if m, ok := firstMimeMatch(src, &h.pointer); ok { + if m, ok := firstMimeMatch(src, &h.filter.pointer); ok { p.dataTarget = k evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.RequestEvent{Type: m}}) return p, evts @@ -891,9 +934,9 @@ func (q *pointerQueue) deliverDropEvent(handlers map[event.Tag]*handler, p point func (q *pointerQueue) deliverTransferCancelEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) { evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.CancelEvent{}}) // Cancel all potential targets. - src := &handlers[p.dataSource].pointer + src := &handlers[p.dataSource].filter.pointer for k, h := range handlers { - if _, ok := firstMimeMatch(src, &h.pointer); ok { + if _, ok := firstMimeMatch(src, &h.filter.pointer); ok { evts = append(evts, taggedEvent{tag: k, event: transfer.CancelEvent{}}) } } @@ -934,9 +977,9 @@ func addHandler(tags []event.Tag, tag event.Tag) []event.Tag { } // firstMimeMatch returns the first type match between src and tgt. -func firstMimeMatch(src, tgt *pointerHandler) (first string, matched bool) { - for _, m1 := range tgt.filter.targetMimes { - for _, m2 := range src.filter.sourceMimes { +func firstMimeMatch(src, tgt *pointerFilter) (first string, matched bool) { + for _, m1 := range tgt.targetMimes { + for _, m2 := range src.sourceMimes { if m1 == m2 { return m1, true } @@ -971,13 +1014,3 @@ func (a *areaNode) bounds() image.Rectangle { Max: a.trans.Transform(f32internal.FPt(a.area.rect.Max)), }.Round() } - -func setScrollEvent(scroll float32, min, max int) (left, scrolled float32) { - if v := float32(max); scroll > v { - return scroll - v, v - } - if v := float32(min); scroll < v { - return scroll - v, v - } - return 0, scroll -} diff --git a/io/input/router.go b/io/input/router.go index 2fabe15c..6982a89d 100644 --- a/io/input/router.go +++ b/io/input/router.go @@ -53,6 +53,9 @@ type Router struct { // transfers is the pending transfer.DataEvent.Open functions. transfers []io.ReadCloser + + // scratchFilter is for garbage-free construction of ephemeral filters. + scratchFilter filter } // Source implements the interface between a Router and user interface widgets. @@ -109,6 +112,18 @@ type handler struct { active bool pointer pointerHandler key keyHandler + // filter the handler has asked for through event handling + // in the previous frame. It is used for routing events in the + // current frame. + filter filter + // prevFilter is the filter being built in the current frame. + nextFilter filter +} + +// filter is the union of a set of [io/event.Filters]. +type filter struct { + pointer pointerFilter + key keyFilter } // stateChange represents the new state and outgoing events @@ -164,27 +179,22 @@ 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 events []event.Event h := q.stateFor(k) + q.scratchFilter.Reset() // Record handler filters and add reset events. for _, f := range filters { - switch f := f.(type) { - case key.Filter: - h.key.Filter(f) + q.scratchFilter.Add(f) + switch f.(type) { case key.FocusFilter: - h.key.Focusable() if reset, ok := h.key.ResetEvent(); ok { events = append(events, reset) } case pointer.Filter: - h.pointer.Filter(k, f) if reset, ok := h.pointer.ResetEvent(); ok { events = append(events, reset) } - case transfer.SourceFilter: - h.pointer.SourceFilter(k, f) - case transfer.TargetFilter: - h.pointer.TargetFilter(k, f) } } + h.nextFilter.Merge(q.scratchFilter) // Accumulate events from state changes until there are no more // matching events. matchedIdx := 0 @@ -193,7 +203,7 @@ func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event { j := 0 for j < len(change.events) { evt := change.events[j] - if evt.tag != k || !filtersMatches(filters, evt.event) { + if evt.tag != k || !q.scratchFilter.Matches(evt.event) { j++ continue } @@ -237,6 +247,8 @@ func (q *Router) Frame(frame *op.Ops) { q.changes = append(q.changes[:0], stateChange{state: state}) } for _, h := range q.handlers { + h.filter, h.nextFilter = h.nextFilter, h.filter + h.nextFilter.Reset() h.pointer.Reset() h.key.Reset() } @@ -276,6 +288,38 @@ func (q *Router) Queue(events ...event.Event) bool { return matched } +func (f *filter) Reset() { + *f = filter{ + key: keyFilter{ + // Re-use filter slice storage. + filters: f.key.filters[:0], + }, + } +} + +func (f *filter) Add(flt event.Filter) { + switch flt := flt.(type) { + case key.Filter: + f.key.Add(flt) + case key.FocusFilter: + f.key.Add(flt) + case pointer.Filter: + f.pointer.Add(flt) + case transfer.SourceFilter, transfer.TargetFilter: + f.pointer.Add(flt) + } +} + +// Merge f2 into f. +func (f *filter) Merge(f2 filter) { + f.key.Merge(f2.key) + f.pointer.Merge(f2.pointer) +} + +func (f *filter) Matches(e event.Event) bool { + return f.key.Matches(e) || f.pointer.Matches(e) +} + func (q *Router) processEvent(e event.Event) bool { state := q.lastState() switch e := e.(type) { @@ -428,7 +472,7 @@ func rangeNorm(r key.Range) key.Range { func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent { f := state.focus var evts []taggedEvent - if f != nil && q.handlers[f].key.Accepts(e) { + if f != nil && q.handlers[f].filter.key.Matches(e) { evts = append(evts, taggedEvent{tag: f, event: e}) return evts } @@ -451,7 +495,7 @@ func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent { if n.tag == nil { continue } - if q.handlers[n.tag].key.Accepts(e) { + if q.handlers[n.tag].filter.key.Matches(e) { evts = append(evts, taggedEvent{tag: n.tag, event: e}) break } @@ -724,51 +768,6 @@ func (q *Router) WakeupTime() (time.Time, bool) { return q.wakeupTime, q.wakeup } -func filtersMatches(filters []event.Filter, e event.Event) bool { - switch e := e.(type) { - case key.Event: - for _, f := range filters { - if f, ok := f.(key.Filter); ok { - if keyFilterMatch(f, e) { - return true - } - } - } - case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent: - for _, f := range filters { - if _, ok := f.(key.FocusFilter); ok { - return true - } - } - case pointer.Event: - for _, f := range filters { - if f, ok := f.(pointer.Filter); ok && f.Kinds&e.Kind == e.Kind { - return true - } - } - case transfer.CancelEvent, transfer.InitiateEvent: - for _, f := range filters { - switch f.(type) { - case transfer.SourceFilter, transfer.TargetFilter: - return true - } - } - case transfer.RequestEvent: - for _, f := range filters { - if f, ok := f.(transfer.SourceFilter); ok && f.Type == e.Type { - return true - } - } - case transfer.DataEvent: - for _, f := range filters { - if f, ok := f.(transfer.TargetFilter); ok && f.Type == e.Type { - return true - } - } - } - return false -} - func decodeInvalidateOp(d []byte) op.InvalidateOp { bo := binary.LittleEndian if ops.OpType(d[0]) != ops.TypeInvalidate {