io/pointer,io/router: [API] make pass-through a property of AreaOp

We're about to make operation scopes explicit, which would result in
both AreaOp and PassOp be scoped. However, PassOp seems to light to have
its separate stack, so this change instead makes pass-through a property
of an area. We're assuming that clients that want pass-through are also
aware of the affected hit area.

API change: replace PassOps with the AreaOp.PassThrough field.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-10-03 18:23:55 +02:00
parent 62daadb37c
commit 6f80b94b4a
6 changed files with 51 additions and 56 deletions
+1 -6
View File
@@ -19,7 +19,6 @@ const (
TypeLinearGradient TypeLinearGradient
TypeArea TypeArea
TypePointerInput TypePointerInput
TypePass
TypeClipboardRead TypeClipboardRead
TypeClipboardWrite TypeClipboardWrite
TypeKeyInput TypeKeyInput
@@ -45,9 +44,8 @@ const (
TypePaintLen = 1 TypePaintLen = 1
TypeColorLen = 1 + 4 TypeColorLen = 1 + 4
TypeLinearGradientLen = 1 + 8*2 + 4*2 TypeLinearGradientLen = 1 + 8*2 + 4*2
TypeAreaLen = 1 + 1 + 4*4 TypeAreaLen = 1 + 1 + 1 + 4*4
TypePointerInputLen = 1 + 1 + 1 + 2*4 + 2*4 TypePointerInputLen = 1 + 1 + 1 + 2*4 + 2*4
TypePassLen = 1 + 1
TypeClipboardReadLen = 1 TypeClipboardReadLen = 1
TypeClipboardWriteLen = 1 TypeClipboardWriteLen = 1
TypeKeyInputLen = 1 + 1 TypeKeyInputLen = 1 + 1
@@ -90,7 +88,6 @@ func (t OpType) Size() int {
TypeLinearGradientLen, TypeLinearGradientLen,
TypeAreaLen, TypeAreaLen,
TypePointerInputLen, TypePointerInputLen,
TypePassLen,
TypeClipboardReadLen, TypeClipboardReadLen,
TypeClipboardWriteLen, TypeClipboardWriteLen,
TypeKeyInputLen, TypeKeyInputLen,
@@ -142,8 +139,6 @@ func (t OpType) String() string {
return "Area" return "Area"
case TypePointerInput: case TypePointerInput:
return "PointerInput" return "PointerInput"
case TypePass:
return "Pass"
case TypeClipboardRead: case TypeClipboardRead:
return "ClipboardRead" return "ClipboardRead"
case TypeClipboardWrite: case TypeClipboardWrite:
+8 -5
View File
@@ -65,14 +65,17 @@ When determining which handlers match an Event, only handlers whose
areas contain the event position are considered. The matching areas contain the event position are considered. The matching
proceeds as follows. proceeds as follows.
First, the foremost matching handler is included. If the handler First, the foremost area that contains the event is found. If no such area
has pass-through enabled, this step is repeated. exists, matching stops.
Then, all matching handlers from the current node and all parent Then, every handler attached to the area or an area in the area stack is
nodes are included. 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.
In the example above, all events will go to h2 only even though both In the example above, all events will go to h2 only even though both
handlers have the same area (the entire screen). handlers have the same area (the implicit area that fills the window).
Pass-through Pass-through
+11 -17
View File
@@ -46,6 +46,10 @@ type Event struct {
// hit area and the area. The area is transformed before applying // hit area and the area. The area is transformed before applying
// it. // it.
type AreaOp struct { type AreaOp struct {
// PassThrough areas and their children don't block events to siblings
// them.
PassThrough bool
kind areaKind kind areaKind
rect image.Rectangle rect image.Rectangle
} }
@@ -72,11 +76,6 @@ type InputOp struct {
ScrollBounds image.Rectangle ScrollBounds image.Rectangle
} }
// PassOp sets the pass-through mode.
type PassOp struct {
Pass bool
}
type ID uint16 type ID uint16
// Type of an Event. // Type of an Event.
@@ -190,11 +189,14 @@ func (op AreaOp) Add(o *op.Ops) {
data := o.Write(opconst.TypeAreaLen) data := o.Write(opconst.TypeAreaLen)
data[0] = byte(opconst.TypeArea) data[0] = byte(opconst.TypeArea)
data[1] = byte(op.kind) data[1] = byte(op.kind)
if op.PassThrough {
data[2] = 1
}
bo := binary.LittleEndian bo := binary.LittleEndian
bo.PutUint32(data[2:], uint32(op.rect.Min.X)) bo.PutUint32(data[3:], uint32(op.rect.Min.X))
bo.PutUint32(data[6:], uint32(op.rect.Min.Y)) bo.PutUint32(data[7:], uint32(op.rect.Min.Y))
bo.PutUint32(data[10:], uint32(op.rect.Max.X)) bo.PutUint32(data[11:], uint32(op.rect.Max.X))
bo.PutUint32(data[14:], uint32(op.rect.Max.Y)) bo.PutUint32(data[15:], uint32(op.rect.Max.Y))
} }
func (op CursorNameOp) Add(o *op.Ops) { func (op CursorNameOp) Add(o *op.Ops) {
@@ -223,14 +225,6 @@ func (op InputOp) Add(o *op.Ops) {
bo.PutUint32(data[15:], uint32(op.ScrollBounds.Max.Y)) bo.PutUint32(data[15:], uint32(op.ScrollBounds.Max.Y))
} }
func (op PassOp) Add(o *op.Ops) {
data := o.Write(opconst.TypePassLen)
data[0] = byte(opconst.TypePass)
if op.Pass {
data[1] = 1
}
}
func (t Type) String() string { func (t Type) String() string {
switch t { switch t {
case Press: case Press:
+22 -21
View File
@@ -31,8 +31,6 @@ type pointerQueue struct {
type hitNode struct { type hitNode struct {
next int next int
area int area int
// Pass tracks the most recent PassOp mode.
pass bool
// For handler nodes. // For handler nodes.
tag event.Tag tag event.Tag
@@ -65,6 +63,7 @@ type pointerHandler struct {
} }
type areaOp struct { type areaOp struct {
pass bool
kind areaKind kind areaKind
rect f32.Rectangle rect f32.Rectangle
} }
@@ -73,6 +72,7 @@ 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
@@ -81,7 +81,6 @@ type areaKind uint8
type collectState struct { type collectState struct {
t f32.Affine2D t f32.Affine2D
node int node int
pass bool
} }
const ( const (
@@ -115,20 +114,18 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
if mask&^opconst.TransformState != 0 { if mask&^opconst.TransformState != 0 {
state = s state = s
} }
case opconst.TypePass:
state.pass = encOp.Data[1] != 0
case opconst.TypeArea: case opconst.TypeArea:
var op areaOp var op areaOp
op.Decode(encOp.Data) op.Decode(encOp.Data)
area := -1 area := -1
if n := state.node; n != -1 { if i := state.node; i != -1 {
area = q.hitTree[n].area n := q.hitTree[i]
area = n.area
} }
q.areas = append(q.areas, areaNode{trans: state.t, next: area, area: op}) q.areas = append(q.areas, areaNode{trans: state.t, next: area, area: op, pass: op.pass})
q.hitTree = append(q.hitTree, hitNode{ q.hitTree = append(q.hitTree, hitNode{
next: state.node, next: state.node,
area: len(q.areas) - 1, area: len(q.areas) - 1,
pass: state.pass,
}) })
state.node = len(q.hitTree) - 1 state.node = len(q.hitTree) - 1
case opconst.TypeTransform: case opconst.TypeTransform:
@@ -141,13 +138,13 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
Types: pointer.Type(encOp.Data[2]), Types: pointer.Type(encOp.Data[2]),
} }
area := -1 area := -1
if n := state.node; n != -1 { if i := state.node; i != -1 {
area = q.hitTree[n].area n := q.hitTree[i]
area = n.area
} }
q.hitTree = append(q.hitTree, hitNode{ q.hitTree = append(q.hitTree, hitNode{
next: state.node, next: state.node,
area: area, area: area,
pass: state.pass,
tag: op.Tag, tag: op.Tag,
}) })
state.node = len(q.hitTree) - 1 state.node = len(q.hitTree) - 1
@@ -189,11 +186,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]
if !q.hit(n.area, pos) { hit, areaPass := q.hit(n.area, pos)
if !hit {
idx-- idx--
continue continue
} }
pass = pass && n.pass pass = pass && areaPass
if pass { if pass {
idx-- idx--
} else { } else {
@@ -214,16 +212,18 @@ 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 { func (q *pointerQueue) hit(areaIdx int, p f32.Point) (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 return false, false
} }
areaIdx = a.next areaIdx = a.next
pass = pass || a.pass
} }
return true return true, pass
} }
func (q *pointerQueue) reset() { func (q *pointerQueue) reset() {
@@ -466,17 +466,18 @@ func (op *areaOp) Decode(d []byte) {
} }
rect := f32.Rectangle{ rect := f32.Rectangle{
Min: f32.Point{ Min: f32.Point{
X: opDecodeFloat32(d[2:]), X: opDecodeFloat32(d[3:]),
Y: opDecodeFloat32(d[6:]), Y: opDecodeFloat32(d[7:]),
}, },
Max: f32.Point{ Max: f32.Point{
X: opDecodeFloat32(d[10:]), X: opDecodeFloat32(d[11:]),
Y: opDecodeFloat32(d[14:]), Y: opDecodeFloat32(d[15:]),
}, },
} }
*op = areaOp{ *op = areaOp{
kind: areaKind(d[1]), kind: areaKind(d[1]),
rect: rect, rect: rect,
pass: d[2] != 0,
} }
} }
+5 -4
View File
@@ -28,11 +28,12 @@ func ExampleClickable_passthrough() {
// widget lays out two buttons on top of each other. // widget lays out two buttons on top of each other.
widget := func() { widget := func() {
// button2 completely covers button1, but PassOp allows pointer
// events to pass through to button1.
button1.Layout(gtx) button1.Layout(gtx)
// PassOp is applied to the area defined by button1. area := pointer.Rect(image.Rectangle{Max: gtx.Constraints.Max})
pointer.PassOp{Pass: true}.Add(gtx.Ops) // button2 completely covers button1, but pass-through allows pointer
// events to pass through to button1.
area.PassThrough = true
area.Add(gtx.Ops)
button2.Layout(gtx) button2.Layout(gtx)
} }
+4 -3
View File
@@ -162,7 +162,7 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta
// Stack a normal clickable area on top of the draggable area // Stack a normal clickable area on top of the draggable area
// to capture non-dragging clicks. // to capture non-dragging clicks.
saved := op.Save(gtx.Ops) saved := op.Save(gtx.Ops)
pointer.PassOp{Pass: true}.Add(gtx.Ops) pointerArea.PassThrough = true
pointerArea.Add(gtx.Ops) pointerArea.Add(gtx.Ops)
s.Scrollbar.AddTrack(gtx.Ops) s.Scrollbar.AddTrack(gtx.Ops)
saved.Load() saved.Load()
@@ -208,8 +208,9 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta
}.Op(gtx.Ops)) }.Op(gtx.Ops))
// Add the indicator pointer hit area. // Add the indicator pointer hit area.
pointer.PassOp{Pass: true}.Add(gtx.Ops) area := pointer.Rect(image.Rectangle{Max: indicatorDims})
pointer.Rect(image.Rectangle{Max: indicatorDims}).Add(gtx.Ops) area.PassThrough = true
area.Add(gtx.Ops)
s.Scrollbar.AddIndicator(gtx.Ops) s.Scrollbar.AddIndicator(gtx.Ops)
return layout.Dimensions{Size: axis.Convert(gtx.Constraints.Min)} return layout.Dimensions{Size: axis.Convert(gtx.Constraints.Min)}