mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
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:
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
Reference in New Issue
Block a user