diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go index c3c20a06..ec6dca77 100644 --- a/internal/opconst/ops.go +++ b/internal/opconst/ops.go @@ -19,7 +19,6 @@ const ( TypeLinearGradient TypeArea TypePointerInput - TypePass TypeClipboardRead TypeClipboardWrite TypeKeyInput @@ -45,9 +44,8 @@ const ( TypePaintLen = 1 TypeColorLen = 1 + 4 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 - TypePassLen = 1 + 1 TypeClipboardReadLen = 1 TypeClipboardWriteLen = 1 TypeKeyInputLen = 1 + 1 @@ -90,7 +88,6 @@ func (t OpType) Size() int { TypeLinearGradientLen, TypeAreaLen, TypePointerInputLen, - TypePassLen, TypeClipboardReadLen, TypeClipboardWriteLen, TypeKeyInputLen, @@ -142,8 +139,6 @@ func (t OpType) String() string { return "Area" case TypePointerInput: return "PointerInput" - case TypePass: - return "Pass" case TypeClipboardRead: return "ClipboardRead" case TypeClipboardWrite: diff --git a/io/pointer/doc.go b/io/pointer/doc.go index 7243b945..559e2743 100644 --- a/io/pointer/doc.go +++ b/io/pointer/doc.go @@ -65,14 +65,17 @@ When determining which handlers match an Event, only handlers whose areas contain the event position are considered. The matching proceeds as follows. -First, the foremost matching handler is included. If the handler -has pass-through enabled, this step is repeated. +First, the foremost area that contains the event is found. If no such area +exists, matching stops. -Then, all matching handlers from the current node and all parent -nodes are included. +Then, every handler attached to the area or an area in the area stack 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. 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 diff --git a/io/pointer/pointer.go b/io/pointer/pointer.go index b495f06d..d592b8f5 100644 --- a/io/pointer/pointer.go +++ b/io/pointer/pointer.go @@ -46,6 +46,10 @@ type Event struct { // hit area and the area. The area is transformed before applying // it. type AreaOp struct { + // PassThrough areas and their children don't block events to siblings + // them. + PassThrough bool + kind areaKind rect image.Rectangle } @@ -72,11 +76,6 @@ type InputOp struct { ScrollBounds image.Rectangle } -// PassOp sets the pass-through mode. -type PassOp struct { - Pass bool -} - type ID uint16 // Type of an Event. @@ -190,11 +189,14 @@ func (op AreaOp) Add(o *op.Ops) { data := o.Write(opconst.TypeAreaLen) data[0] = byte(opconst.TypeArea) data[1] = byte(op.kind) + if op.PassThrough { + data[2] = 1 + } bo := binary.LittleEndian - bo.PutUint32(data[2:], uint32(op.rect.Min.X)) - bo.PutUint32(data[6:], uint32(op.rect.Min.Y)) - bo.PutUint32(data[10:], uint32(op.rect.Max.X)) - bo.PutUint32(data[14:], uint32(op.rect.Max.Y)) + bo.PutUint32(data[3:], uint32(op.rect.Min.X)) + bo.PutUint32(data[7:], uint32(op.rect.Min.Y)) + bo.PutUint32(data[11:], uint32(op.rect.Max.X)) + bo.PutUint32(data[15:], uint32(op.rect.Max.Y)) } 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)) } -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 { switch t { case Press: diff --git a/io/router/pointer.go b/io/router/pointer.go index 229960ba..579e6e42 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -31,8 +31,6 @@ type pointerQueue struct { type hitNode struct { next int area int - // Pass tracks the most recent PassOp mode. - pass bool // For handler nodes. tag event.Tag @@ -65,6 +63,7 @@ type pointerHandler struct { } type areaOp struct { + pass bool kind areaKind rect f32.Rectangle } @@ -73,6 +72,7 @@ type areaNode struct { trans f32.Affine2D next int area areaOp + pass bool } type areaKind uint8 @@ -81,7 +81,6 @@ type areaKind uint8 type collectState struct { t f32.Affine2D node int - pass bool } const ( @@ -115,20 +114,18 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { if mask&^opconst.TransformState != 0 { state = s } - case opconst.TypePass: - state.pass = encOp.Data[1] != 0 case opconst.TypeArea: var op areaOp op.Decode(encOp.Data) area := -1 - if n := state.node; n != -1 { - area = q.hitTree[n].area + if i := state.node; i != -1 { + 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{ next: state.node, area: len(q.areas) - 1, - pass: state.pass, }) state.node = len(q.hitTree) - 1 case opconst.TypeTransform: @@ -141,13 +138,13 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { Types: pointer.Type(encOp.Data[2]), } area := -1 - if n := state.node; n != -1 { - area = q.hitTree[n].area + if i := state.node; i != -1 { + n := q.hitTree[i] + area = n.area } q.hitTree = append(q.hitTree, hitNode{ next: state.node, area: area, - pass: state.pass, tag: op.Tag, }) 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 for idx >= 0 { n := &q.hitTree[idx] - if !q.hit(n.area, pos) { + hit, areaPass := q.hit(n.area, pos) + if !hit { idx-- continue } - pass = pass && n.pass + pass = pass && areaPass if pass { idx-- } else { @@ -214,16 +212,18 @@ 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 { +func (q *pointerQueue) hit(areaIdx int, p f32.Point) (bool, bool) { + pass := false for areaIdx != -1 { a := &q.areas[areaIdx] p := a.trans.Invert().Transform(p) if !a.area.Hit(p) { - return false + return false, false } areaIdx = a.next + pass = pass || a.pass } - return true + return true, pass } func (q *pointerQueue) reset() { @@ -466,17 +466,18 @@ func (op *areaOp) Decode(d []byte) { } rect := f32.Rectangle{ Min: f32.Point{ - X: opDecodeFloat32(d[2:]), - Y: opDecodeFloat32(d[6:]), + X: opDecodeFloat32(d[3:]), + Y: opDecodeFloat32(d[7:]), }, Max: f32.Point{ - X: opDecodeFloat32(d[10:]), - Y: opDecodeFloat32(d[14:]), + X: opDecodeFloat32(d[11:]), + Y: opDecodeFloat32(d[15:]), }, } *op = areaOp{ kind: areaKind(d[1]), rect: rect, + pass: d[2] != 0, } } diff --git a/widget/example_test.go b/widget/example_test.go index 185643b1..7cda7ca1 100644 --- a/widget/example_test.go +++ b/widget/example_test.go @@ -28,11 +28,12 @@ func ExampleClickable_passthrough() { // widget lays out two buttons on top of each other. widget := func() { - // button2 completely covers button1, but PassOp allows pointer - // events to pass through to button1. button1.Layout(gtx) - // PassOp is applied to the area defined by button1. - pointer.PassOp{Pass: true}.Add(gtx.Ops) + area := pointer.Rect(image.Rectangle{Max: gtx.Constraints.Max}) + // 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) } diff --git a/widget/material/list.go b/widget/material/list.go index 281c77b9..2037bbaa 100644 --- a/widget/material/list.go +++ b/widget/material/list.go @@ -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 // to capture non-dragging clicks. saved := op.Save(gtx.Ops) - pointer.PassOp{Pass: true}.Add(gtx.Ops) + pointerArea.PassThrough = true pointerArea.Add(gtx.Ops) s.Scrollbar.AddTrack(gtx.Ops) saved.Load() @@ -208,8 +208,9 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta }.Op(gtx.Ops)) // Add the indicator pointer hit area. - pointer.PassOp{Pass: true}.Add(gtx.Ops) - pointer.Rect(image.Rectangle{Max: indicatorDims}).Add(gtx.Ops) + area := pointer.Rect(image.Rectangle{Max: indicatorDims}) + area.PassThrough = true + area.Add(gtx.Ops) s.Scrollbar.AddIndicator(gtx.Ops) return layout.Dimensions{Size: axis.Convert(gtx.Constraints.Min)}