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 <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-10-24 16:40:40 +02:00
parent 563eb60382
commit 8cafbd309f
3 changed files with 72 additions and 26 deletions
+15 -16
View File
@@ -59,30 +59,29 @@ For example:
implies a tree of two inner nodes, each with one pointer handler attached. implies a tree of two inner nodes, each with one pointer handler attached.
When determining which handlers match an Event, only handlers whose The matching proceeds as follows.
areas contain the event position are considered. The matching
proceeds as follows.
First, the foremost area that contains the event is found. If no such area First, the foremost area that contains the event is found. Only areas whose
exists, matching stops. parent areas all contain the event is considered.
Then, every handler attached to the area or an area in the area stack is Then, every handler attached to the area is matched with the event.
matched with the event.
Third, If the area or any area in the area stack has pass-through enabled, If all attached handlers are marked pass-through, the matching repeats with the
the matching repeats with the next foremost area. 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 In the example above, all events will go to h2 because it and h1 are siblings
handlers have the same area (the implicit area that fills the window). and none are pass-through.
Pass-through Pass-through
The PassOp operations controls the pass-through setting. A handler's The PassOp operations controls the pass-through setting. All handlers added
pass-through setting is recorded along with the InputOp. inside one or more PassOp scopes are marked pass-through.
Pass-through handlers are useful for overlay widgets such as a hidden Pass-through is useful for overlay widgets. Consider a hidden side drawer: when
side drawer. When the user touches the side, both the (transparent) the user touches the side, both the (transparent) drawer handle and the
drawer handle and the interface below should receive pointer events. interface below should receive pointer events. This effect is achieved by
marking the drawer handle pass-through.
Disambiguation Disambiguation
+10 -10
View File
@@ -28,7 +28,8 @@ type hitNode struct {
area int area int
// For handler nodes. // For handler nodes.
tag event.Tag tag event.Tag
pass bool
} }
type cursorNode struct { type cursorNode struct {
@@ -66,7 +67,6 @@ type areaNode struct {
trans f32.Affine2D trans f32.Affine2D
next int next int
area areaOp area areaOp
pass bool
} }
type areaKind uint8 type areaKind uint8
@@ -113,11 +113,12 @@ func (c *pointerCollector) area(op areaOp) {
n := c.q.hitTree[i] n := c.q.hitTree[i]
area = n.area 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.nodeStack = append(c.nodeStack, c.state.nodePlusOne-1)
c.q.hitTree = append(c.q.hitTree, hitNode{ c.q.hitTree = append(c.q.hitTree, hitNode{
next: c.state.nodePlusOne - 1, next: c.state.nodePlusOne - 1,
area: len(c.q.areas) - 1, area: len(c.q.areas) - 1,
pass: true,
}) })
c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1 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, next: c.state.nodePlusOne - 1,
area: area, area: area,
tag: op.Tag, tag: op.Tag,
pass: c.state.pass > 0,
}) })
c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1 c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1
h, ok := c.q.handlers[op.Tag] 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 idx := len(q.hitTree) - 1
for idx >= 0 { for idx >= 0 {
n := &q.hitTree[idx] n := &q.hitTree[idx]
hit, areaPass := q.hit(n.area, pos) hit := q.hit(n.area, pos, n.pass)
if !hit { if !hit {
idx-- idx--
continue continue
} }
pass = pass && areaPass pass = pass && n.pass
if pass { if pass {
idx-- idx--
} else { } else {
@@ -223,18 +225,16 @@ func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
return q.areas[areaIdx].trans.Invert().Transform(p) return q.areas[areaIdx].trans.Invert().Transform(p)
} }
func (q *pointerQueue) hit(areaIdx int, p f32.Point) (bool, bool) { func (q *pointerQueue) hit(areaIdx int, p f32.Point, pass bool) bool {
pass := false
for areaIdx != -1 { for areaIdx != -1 {
a := &q.areas[areaIdx] a := &q.areas[areaIdx]
p := a.trans.Invert().Transform(p) p := a.trans.Invert().Transform(p)
if !a.area.Hit(p) { if !a.area.Hit(p) {
return false, false return false
} }
areaIdx = a.next areaIdx = a.next
pass = pass || a.pass
} }
return true, pass return true
} }
func (q *pointerQueue) reset() { func (q *pointerQueue) reset() {
+47
View File
@@ -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 // addPointerHandler adds a pointer.InputOp for the tag in a
// rectangular area. // rectangular area.
func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) { func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {