diff --git a/internal/ops/ops.go b/internal/ops/ops.go index 0219689d..31d3e532 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -23,7 +23,7 @@ type Ops struct { nextStateID int macroStack stack - stacks [3]stack + stacks [4]stack } type OpType byte @@ -45,6 +45,8 @@ const ( TypeLinearGradient TypeArea TypePopArea + TypePass + TypePopPass TypePointerInput TypeClipboardRead TypeClipboardWrite @@ -88,6 +90,7 @@ const ( ClipStack StackKind = iota AreaStack TransStack + PassStack ) const ( @@ -102,8 +105,10 @@ const ( TypePaintLen = 1 TypeColorLen = 1 + 4 TypeLinearGradientLen = 1 + 8*2 + 4*2 - TypeAreaLen = 1 + 1 + 1 + 4*4 + TypeAreaLen = 1 + 1 + 4*4 TypePopAreaLen = 1 + TypePassLen = 1 + TypePopPassLen = 1 TypePointerInputLen = 1 + 1 + 1 + 2*4 + 2*4 TypeClipboardReadLen = 1 TypeClipboardWriteLen = 1 @@ -313,6 +318,8 @@ func (t OpType) Size() int { TypeLinearGradientLen, TypeAreaLen, TypePopAreaLen, + TypePassLen, + TypePopPassLen, TypePointerInputLen, TypeClipboardReadLen, TypeClipboardWriteLen, @@ -370,6 +377,10 @@ func (t OpType) String() string { return "Area" case TypePopArea: return "PopArea" + case TypePass: + return "Pass" + case TypePopPass: + return "PopPass" case TypePointerInput: return "PointerInput" case TypeClipboardRead: diff --git a/io/pointer/pointer.go b/io/pointer/pointer.go index 9e639c0d..d30e4338 100644 --- a/io/pointer/pointer.go +++ b/io/pointer/pointer.go @@ -42,14 +42,9 @@ type Event struct { Modifiers key.Modifiers } -// AreaOp updates the hit area to the intersection of the current -// hit area and the area. The area is transformed before applying -// it. +// AreaOp pushes the current hit area to the stack and updates it to the +// intersection of the current hit area and the transformed area. type AreaOp struct { - // PassThrough areas and their children don't block events to siblings - // them. - PassThrough bool - kind areaKind rect image.Rectangle } @@ -61,6 +56,18 @@ type AreaStack struct { macroID int } +// PassOp sets the pass-through mode. AreaOps added while the pass-through +// mode is set don't block events to siblings. +type PassOp struct { +} + +// PassStack represents a PassOp on the pass stack. +type PassStack struct { + ops *ops.Ops + id ops.StackID + macroID int +} + // CursorNameOp sets the cursor for the current area. type CursorNameOp struct { Name CursorName @@ -204,14 +211,11 @@ func (a AreaOp) add(o *op.Ops, push bool) { data := o.Internal.Write(ops.TypeAreaLen) data[0] = byte(ops.TypeArea) data[1] = byte(a.kind) - if a.PassThrough { - data[2] = 1 - } bo := binary.LittleEndian - bo.PutUint32(data[3:], uint32(a.rect.Min.X)) - bo.PutUint32(data[7:], uint32(a.rect.Min.Y)) - bo.PutUint32(data[11:], uint32(a.rect.Max.X)) - bo.PutUint32(data[15:], uint32(a.rect.Max.Y)) + bo.PutUint32(data[2:], uint32(a.rect.Min.X)) + bo.PutUint32(data[6:], uint32(a.rect.Min.Y)) + bo.PutUint32(data[10:], uint32(a.rect.Max.X)) + bo.PutUint32(data[14:], uint32(a.rect.Max.Y)) } func (o AreaStack) Pop() { @@ -220,6 +224,20 @@ func (o AreaStack) Pop() { data[0] = byte(ops.TypePopArea) } +// Push the current pass mode to the pass stack and set the pass mode. +func (p PassOp) Push(o *op.Ops) PassStack { + id, mid := o.Internal.PushOp(ops.PassStack) + data := o.Internal.Write(ops.TypePassLen) + data[0] = byte(ops.TypePass) + return PassStack{ops: &o.Internal, id: id, macroID: mid} +} + +func (p PassStack) Pop() { + p.ops.PopOp(ops.PassStack, p.id, p.macroID) + data := p.ops.Write(ops.TypePopPassLen) + data[0] = byte(ops.TypePopPass) +} + func (op CursorNameOp) Add(o *op.Ops) { data := o.Internal.Write1(ops.TypeCursorLen, op.Name) data[0] = byte(ops.TypeCursor) diff --git a/io/router/pointer.go b/io/router/pointer.go index 0e3bc972..d896c656 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -64,7 +64,6 @@ type pointerHandler struct { } type areaOp struct { - pass bool kind areaKind rect f32.Rectangle } @@ -82,7 +81,7 @@ type areaKind uint8 type collectState struct { t f32.Affine2D node int - pass bool + pass int } const ( @@ -122,7 +121,7 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { n := q.hitTree[i] area = n.area } - q.areas = append(q.areas, areaNode{trans: state.t, next: area, area: op, pass: op.pass}) + q.areas = append(q.areas, areaNode{trans: state.t, next: area, area: op, pass: state.pass > 0}) q.nodeStack = append(q.nodeStack, state.node) q.hitTree = append(q.hitTree, hitNode{ next: state.node, @@ -133,6 +132,10 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { n := len(q.nodeStack) state.node = q.nodeStack[n-1] q.nodeStack = q.nodeStack[:n-1] + case ops.TypePass: + state.pass++ + case ops.TypePopPass: + state.pass-- case ops.TypeTransform: dop, push := ops.DecodeTransform(encOp.Data) if push { @@ -480,18 +483,17 @@ func (op *areaOp) Decode(d []byte) { } rect := f32.Rectangle{ Min: f32.Point{ - X: opDecodeFloat32(d[3:]), - Y: opDecodeFloat32(d[7:]), + X: opDecodeFloat32(d[2:]), + Y: opDecodeFloat32(d[6:]), }, Max: f32.Point{ - X: opDecodeFloat32(d[11:]), - Y: opDecodeFloat32(d[15:]), + X: opDecodeFloat32(d[10:]), + Y: opDecodeFloat32(d[14:]), }, } *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 151e005a..c4714061 100644 --- a/widget/example_test.go +++ b/widget/example_test.go @@ -29,11 +29,9 @@ func ExampleClickable_passthrough() { // widget lays out two buttons on top of each other. widget := func() { button1.Layout(gtx) - 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 - defer area.Push(gtx.Ops).Pop() + defer pointer.PassOp{}.Push(gtx.Ops).Pop() button2.Layout(gtx) } diff --git a/widget/material/list.go b/widget/material/list.go index f6310982..b9ca7be3 100644 --- a/widget/material/list.go +++ b/widget/material/list.go @@ -161,7 +161,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. - pointerArea.PassThrough = true + defer pointer.PassOp{}.Push(gtx.Ops).Pop() defer pointerArea.Push(gtx.Ops).Pop() s.Scrollbar.AddTrack(gtx.Ops) @@ -206,7 +206,7 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta // Add the indicator pointer hit area. area := pointer.Rect(image.Rectangle{Max: indicatorDims}) - area.PassThrough = true + defer pointer.PassOp{}.Push(gtx.Ops).Pop() defer area.Push(gtx.Ops).Pop() s.Scrollbar.AddIndicator(gtx.Ops)