From 8cafbd309f079f63bbe3e63dc66f43bb5eb170c3 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 24 Oct 2021 16:40:40 +0200 Subject: [PATCH] io/pointer,io/router: make PassOp apply to InputOps, not areas We're about to make clip.Ops act as pointer areas, in which case we'd like to contain the effect of PassOp to just pointer InputOps. Signed-off-by: Elias Naur --- io/pointer/doc.go | 31 +++++++++++++------------- io/router/pointer.go | 20 ++++++++--------- io/router/pointer_test.go | 47 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/io/pointer/doc.go b/io/pointer/doc.go index 1c22d7e5..0abb3550 100644 --- a/io/pointer/doc.go +++ b/io/pointer/doc.go @@ -59,30 +59,29 @@ For example: implies a tree of two inner nodes, each with one pointer handler attached. -When determining which handlers match an Event, only handlers whose -areas contain the event position are considered. The matching -proceeds as follows. +The matching proceeds as follows. -First, the foremost area that contains the event is found. If no such area -exists, matching stops. +First, the foremost area that contains the event is found. Only areas whose +parent areas all contain the event is considered. -Then, every handler attached to the area or an area in the area stack is -matched with the event. +Then, every handler attached to the area is matched with the event. -Third, If the area or any area in the area stack has pass-through enabled, -the matching repeats with the next foremost area. +If all attached handlers are marked pass-through, the matching repeats with the +next foremost (sibling) area. Otherwise the matching repeats with the parent +area. -In the example above, all events will go to h2 only even though both -handlers have the same area (the implicit area that fills the window). +In the example above, all events will go to h2 because it and h1 are siblings +and none are pass-through. Pass-through -The PassOp operations controls the pass-through setting. A handler's -pass-through setting is recorded along with the InputOp. +The PassOp operations controls the pass-through setting. All handlers added +inside one or more PassOp scopes are marked pass-through. -Pass-through handlers are useful for overlay widgets such as a hidden -side drawer. When the user touches the side, both the (transparent) -drawer handle and the interface below should receive pointer events. +Pass-through is useful for overlay widgets. Consider a hidden side drawer: when +the user touches the side, both the (transparent) drawer handle and the +interface below should receive pointer events. This effect is achieved by +marking the drawer handle pass-through. Disambiguation diff --git a/io/router/pointer.go b/io/router/pointer.go index 0638dfec..1e6e50f7 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -28,7 +28,8 @@ type hitNode struct { area int // For handler nodes. - tag event.Tag + tag event.Tag + pass bool } type cursorNode struct { @@ -66,7 +67,6 @@ type areaNode struct { trans f32.Affine2D next int area areaOp - pass bool } type areaKind uint8 @@ -113,11 +113,12 @@ func (c *pointerCollector) area(op areaOp) { n := c.q.hitTree[i] area = n.area } - c.q.areas = append(c.q.areas, areaNode{trans: c.state.t, next: area, area: op, pass: c.state.pass > 0}) + c.q.areas = append(c.q.areas, areaNode{trans: c.state.t, next: area, area: op}) c.nodeStack = append(c.nodeStack, c.state.nodePlusOne-1) c.q.hitTree = append(c.q.hitTree, hitNode{ next: c.state.nodePlusOne - 1, area: len(c.q.areas) - 1, + pass: true, }) c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1 } @@ -159,6 +160,7 @@ func (c *pointerCollector) inputOp(op pointer.InputOp, events *handlerEvents) { next: c.state.nodePlusOne - 1, area: area, tag: op.Tag, + pass: c.state.pass > 0, }) c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1 h, ok := c.q.handlers[op.Tag] @@ -197,12 +199,12 @@ func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) { idx := len(q.hitTree) - 1 for idx >= 0 { n := &q.hitTree[idx] - hit, areaPass := q.hit(n.area, pos) + hit := q.hit(n.area, pos, n.pass) if !hit { idx-- continue } - pass = pass && areaPass + pass = pass && n.pass if pass { idx-- } else { @@ -223,18 +225,16 @@ func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point { return q.areas[areaIdx].trans.Invert().Transform(p) } -func (q *pointerQueue) hit(areaIdx int, p f32.Point) (bool, bool) { - pass := false +func (q *pointerQueue) hit(areaIdx int, p f32.Point, pass bool) bool { for areaIdx != -1 { a := &q.areas[areaIdx] p := a.trans.Invert().Transform(p) if !a.area.Hit(p) { - return false, false + return false } areaIdx = a.next - pass = pass || a.pass } - return true, pass + return true } func (q *pointerQueue) reset() { diff --git a/io/router/pointer_test.go b/io/router/pointer_test.go index f86fec12..7bc981c0 100644 --- a/io/router/pointer_test.go +++ b/io/router/pointer_test.go @@ -686,6 +686,53 @@ func TestCursorNameOp(t *testing.T) { } } +func TestPassOp(t *testing.T) { + var ops op.Ops + + h1, h2, h3, h4 := new(int), new(int), new(int), new(int) + area := pointer.Rect(image.Rect(0, 0, 100, 100)) + root := area.Push(&ops) + pointer.InputOp{Tag: h1, Types: pointer.Press}.Add(&ops) + child1 := area.Push(&ops) + pointer.InputOp{Tag: h2, Types: pointer.Press}.Add(&ops) + child1.Pop() + child2 := area.Push(&ops) + pass := pointer.PassOp{}.Push(&ops) + pointer.InputOp{Tag: h3, Types: pointer.Press}.Add(&ops) + pointer.InputOp{Tag: h4, Types: pointer.Press}.Add(&ops) + pass.Pop() + child2.Pop() + root.Pop() + + var r Router + r.Frame(&ops) + r.Queue( + pointer.Event{ + Type: pointer.Press, + }, + ) + assertEventSequence(t, r.Events(h1), pointer.Cancel, pointer.Press) + assertEventSequence(t, r.Events(h2), pointer.Cancel, pointer.Press) + assertEventSequence(t, r.Events(h3), pointer.Cancel, pointer.Press) + assertEventSequence(t, r.Events(h4), pointer.Cancel, pointer.Press) +} + +func TestAreaPassthrough(t *testing.T) { + var ops op.Ops + + h := new(int) + pointer.InputOp{Tag: h, Types: pointer.Press}.Add(&ops) + pointer.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop() + var r Router + r.Frame(&ops) + r.Queue( + pointer.Event{ + Type: pointer.Press, + }, + ) + assertEventSequence(t, r.Events(h), pointer.Cancel, pointer.Press) +} + // addPointerHandler adds a pointer.InputOp for the tag in a // rectangular area. func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {