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) {