mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 10:25:37 +00:00
io/pointer,io/router: replace AreaOp with clip.Op
Pointer hit areas and paint clip areas are separate concepts, but similar enough to warrant merging. This change replaces pointer hit areas with clip areas, so Gio is left with just one area concept (in package op/clip). The reason for separating the concepts in the original Gio release was because of my being unsure general path/stroke hit areas would ever be implemented, let alone efficient. This change represents a change of mind, in the sense that it's better to have an incomplete API than two separate area concepts. Leave the deprecated pointer.Rect, pointer.Ellipse for temporary backwards compatibility. This is an API change. Most existing programs should continue to build with this change, but may have to adjust to having all clip.Ops participate in InputOp hit areas. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+11
-12
@@ -29,6 +29,8 @@ type Ops struct {
|
|||||||
|
|
||||||
type OpType byte
|
type OpType byte
|
||||||
|
|
||||||
|
type Shape byte
|
||||||
|
|
||||||
// Start at a high number for easier debugging.
|
// Start at a high number for easier debugging.
|
||||||
const firstOpIndex = 200
|
const firstOpIndex = 200
|
||||||
|
|
||||||
@@ -44,8 +46,6 @@ const (
|
|||||||
TypePaint
|
TypePaint
|
||||||
TypeColor
|
TypeColor
|
||||||
TypeLinearGradient
|
TypeLinearGradient
|
||||||
TypeArea
|
|
||||||
TypePopArea
|
|
||||||
TypePass
|
TypePass
|
||||||
TypePopPass
|
TypePopPass
|
||||||
TypePointerInput
|
TypePointerInput
|
||||||
@@ -91,15 +91,21 @@ type StackKind uint8
|
|||||||
type ClipOp struct {
|
type ClipOp struct {
|
||||||
Bounds image.Rectangle
|
Bounds image.Rectangle
|
||||||
Outline bool
|
Outline bool
|
||||||
|
Shape Shape
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ClipStack StackKind = iota
|
ClipStack StackKind = iota
|
||||||
AreaStack
|
|
||||||
TransStack
|
TransStack
|
||||||
PassStack
|
PassStack
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Path Shape = iota
|
||||||
|
Ellipse
|
||||||
|
Rect
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeMacroLen = 1 + 4 + 4
|
TypeMacroLen = 1 + 4 + 4
|
||||||
TypeCallLen = 1 + 4 + 4
|
TypeCallLen = 1 + 4 + 4
|
||||||
@@ -112,8 +118,6 @@ 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
|
|
||||||
TypePopAreaLen = 1
|
|
||||||
TypePassLen = 1
|
TypePassLen = 1
|
||||||
TypePopPassLen = 1
|
TypePopPassLen = 1
|
||||||
TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4
|
TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4
|
||||||
@@ -125,7 +129,7 @@ const (
|
|||||||
TypeSaveLen = 1 + 4
|
TypeSaveLen = 1 + 4
|
||||||
TypeLoadLen = 1 + 4
|
TypeLoadLen = 1 + 4
|
||||||
TypeAuxLen = 1
|
TypeAuxLen = 1
|
||||||
TypeClipLen = 1 + 4*4 + 1
|
TypeClipLen = 1 + 4*4 + 1 + 1
|
||||||
TypePopClipLen = 1
|
TypePopClipLen = 1
|
||||||
TypeProfileLen = 1
|
TypeProfileLen = 1
|
||||||
TypeCursorLen = 1 + 1
|
TypeCursorLen = 1 + 1
|
||||||
@@ -151,6 +155,7 @@ func (op *ClipOp) Decode(data []byte) {
|
|||||||
*op = ClipOp{
|
*op = ClipOp{
|
||||||
Bounds: r,
|
Bounds: r,
|
||||||
Outline: data[17] == 1,
|
Outline: data[17] == 1,
|
||||||
|
Shape: Shape(data[18]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,8 +337,6 @@ func (t OpType) Size() int {
|
|||||||
TypePaintLen,
|
TypePaintLen,
|
||||||
TypeColorLen,
|
TypeColorLen,
|
||||||
TypeLinearGradientLen,
|
TypeLinearGradientLen,
|
||||||
TypeAreaLen,
|
|
||||||
TypePopAreaLen,
|
|
||||||
TypePassLen,
|
TypePassLen,
|
||||||
TypePopPassLen,
|
TypePopPassLen,
|
||||||
TypePointerInputLen,
|
TypePointerInputLen,
|
||||||
@@ -389,10 +392,6 @@ func (t OpType) String() string {
|
|||||||
return "Color"
|
return "Color"
|
||||||
case TypeLinearGradient:
|
case TypeLinearGradient:
|
||||||
return "LinearGradient"
|
return "LinearGradient"
|
||||||
case TypeArea:
|
|
||||||
return "Area"
|
|
||||||
case TypePopArea:
|
|
||||||
return "PopArea"
|
|
||||||
case TypePass:
|
case TypePass:
|
||||||
return "Pass"
|
return "Pass"
|
||||||
case TypePopPass:
|
case TypePopPass:
|
||||||
|
|||||||
+16
-12
@@ -25,19 +25,23 @@ Leave, or Scroll):
|
|||||||
|
|
||||||
Cancel events are always delivered.
|
Cancel events are always delivered.
|
||||||
|
|
||||||
Areas
|
Hit areas
|
||||||
|
|
||||||
The area operations are used for specifying the area where
|
Clip operations from package op/clip are used for specifying
|
||||||
subsequent InputOp are active.
|
hit areas where subsequent InputOps are active.
|
||||||
|
|
||||||
For example, to set up a rectangular hit area:
|
For example, to set up a handler with a rectangular hit area:
|
||||||
|
|
||||||
r := image.Rectangle{...}
|
r := image.Rectangle{...}
|
||||||
pointer.Rect(r).Add(ops)
|
area := clip.Rect(r).Push(ops)
|
||||||
pointer.InputOp{Tag: h}.Add(ops)
|
pointer.InputOp{Tag: h}.Add(ops)
|
||||||
|
area.Pop()
|
||||||
|
|
||||||
Note that areas compound: the effective area of multiple area
|
Note that hit areas behave similar to painting: the effective area of a stack
|
||||||
operations is the intersection of the areas.
|
of multiple area operations is the intersection of the areas.
|
||||||
|
|
||||||
|
BUG: Clip operations other than clip.Rect and clip.Ellipse are approximated
|
||||||
|
with their bounding boxes.
|
||||||
|
|
||||||
Matching events
|
Matching events
|
||||||
|
|
||||||
@@ -49,11 +53,11 @@ For example:
|
|||||||
ops := new(op.Ops)
|
ops := new(op.Ops)
|
||||||
var h1, h2 *Handler
|
var h1, h2 *Handler
|
||||||
|
|
||||||
area := pointer.Rect(...).Push(ops)
|
area := clip.Rect(...).Push(ops)
|
||||||
pointer.InputOp{Tag: h1}.Add(Ops)
|
pointer.InputOp{Tag: h1}.Add(Ops)
|
||||||
area.Pop()
|
area.Pop()
|
||||||
|
|
||||||
area := pointer.Rect(...).Push(ops)
|
area := clip.Rect(...).Push(ops)
|
||||||
pointer.InputOp{Tag: h2}.Add(ops)
|
pointer.InputOp{Tag: h2}.Add(ops)
|
||||||
area.Pop()
|
area.Pop()
|
||||||
|
|
||||||
@@ -66,9 +70,9 @@ parent areas all contain the event is considered.
|
|||||||
|
|
||||||
Then, every handler attached to the area is matched with the event.
|
Then, every handler attached to the area is matched with the event.
|
||||||
|
|
||||||
If all attached handlers are marked pass-through, the matching repeats with the
|
If all attached handlers are marked pass-through or if no handlers are
|
||||||
next foremost (sibling) area. Otherwise the matching repeats with the parent
|
attached, the matching repeats with the next foremost (sibling) area. Otherwise
|
||||||
area.
|
the matching repeats with the parent area.
|
||||||
|
|
||||||
In the example above, all events will go to h2 because it and h1 are siblings
|
In the example above, all events will go to h2 because it and h1 are siblings
|
||||||
and none are pass-through.
|
and none are pass-through.
|
||||||
|
|||||||
+20
-54
@@ -14,6 +14,7 @@ import (
|
|||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event is a pointer event.
|
// Event is a pointer event.
|
||||||
@@ -42,20 +43,6 @@ type Event struct {
|
|||||||
Modifiers key.Modifiers
|
Modifiers key.Modifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
|
||||||
kind areaKind
|
|
||||||
rect image.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaStack represents an AreaOp on the stack of areas.
|
|
||||||
type AreaStack struct {
|
|
||||||
ops *ops.Ops
|
|
||||||
id ops.StackID
|
|
||||||
macroID int
|
|
||||||
}
|
|
||||||
|
|
||||||
// PassOp sets the pass-through mode. InputOps added while the pass-through
|
// PassOp sets the pass-through mode. InputOps added while the pass-through
|
||||||
// mode is set don't block events to siblings.
|
// mode is set don't block events to siblings.
|
||||||
type PassOp struct {
|
type PassOp struct {
|
||||||
@@ -107,9 +94,6 @@ type Buttons uint8
|
|||||||
// CursorName is the name of a cursor.
|
// CursorName is the name of a cursor.
|
||||||
type CursorName string
|
type CursorName string
|
||||||
|
|
||||||
// Must match app/internal/input.areaKind
|
|
||||||
type areaKind uint8
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// CursorDefault is the default cursor.
|
// CursorDefault is the default cursor.
|
||||||
CursorDefault CursorName = ""
|
CursorDefault CursorName = ""
|
||||||
@@ -178,50 +162,32 @@ const (
|
|||||||
ButtonTertiary
|
ButtonTertiary
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
areaRect areaKind = iota
|
|
||||||
areaEllipse
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rect constructs a rectangular hit area.
|
// Rect constructs a rectangular hit area.
|
||||||
func Rect(size image.Rectangle) AreaOp {
|
//
|
||||||
return AreaOp{
|
// Deprecated: use clip.Rect instead.
|
||||||
kind: areaRect,
|
func Rect(size image.Rectangle) clip.Op {
|
||||||
rect: size,
|
return clip.Rect(size).Op()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ellipse constructs an ellipsoid hit area.
|
// Ellipse constructs an ellipsoid hit area.
|
||||||
func Ellipse(size image.Rectangle) AreaOp {
|
//
|
||||||
return AreaOp{
|
// Deprecated: use clip.Ellipse instead.
|
||||||
kind: areaEllipse,
|
func Ellipse(size image.Rectangle) clip.Ellipse {
|
||||||
rect: size,
|
return clip.Ellipse(frect(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// frect converts a rectangle to a f32.Rectangle.
|
||||||
|
func frect(r image.Rectangle) f32.Rectangle {
|
||||||
|
return f32.Rectangle{
|
||||||
|
Min: fpt(r.Min), Max: fpt(r.Max),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the current area to the stack and intersects the current area with the
|
// fpt converts an point to a f32.Point.
|
||||||
// area represented by o.
|
func fpt(p image.Point) f32.Point {
|
||||||
func (a AreaOp) Push(o *op.Ops) AreaStack {
|
return f32.Point{
|
||||||
id, macroID := ops.PushOp(&o.Internal, ops.AreaStack)
|
X: float32(p.X), Y: float32(p.Y),
|
||||||
a.add(o, true)
|
}
|
||||||
return AreaStack{ops: &o.Internal, id: id, macroID: macroID}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AreaOp) add(o *op.Ops, push bool) {
|
|
||||||
data := ops.Write(&o.Internal, ops.TypeAreaLen)
|
|
||||||
data[0] = byte(ops.TypeArea)
|
|
||||||
data[1] = byte(a.kind)
|
|
||||||
bo := binary.LittleEndian
|
|
||||||
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() {
|
|
||||||
ops.PopOp(o.ops, ops.AreaStack, o.id, o.macroID)
|
|
||||||
data := ops.Write(o.ops, ops.TypePopAreaLen)
|
|
||||||
data[0] = byte(ops.TypePopArea)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the current pass mode to the pass stack and set the pass mode.
|
// Push the current pass mode to the pass stack and set the pass mode.
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ package pointer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gioui.org/op"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTypeString(t *testing.T) {
|
func TestTypeString(t *testing.T) {
|
||||||
@@ -33,15 +31,3 @@ func TestTypeString(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransformChecks(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err == nil {
|
|
||||||
t.Error("cross-macro Pop didn't panic")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
var ops op.Ops
|
|
||||||
area := AreaOp{}.Push(&ops)
|
|
||||||
op.Record(&ops)
|
|
||||||
area.Pop()
|
|
||||||
}
|
|
||||||
|
|||||||
+21
-22
@@ -107,13 +107,18 @@ func (c *pointerCollector) load(id int) {
|
|||||||
c.state = collectState{t: c.states[id]}
|
c.state = collectState{t: c.states[id]}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *pointerCollector) area(op areaOp) {
|
func (c *pointerCollector) clip(op ops.ClipOp) {
|
||||||
area := -1
|
area := -1
|
||||||
if i := c.state.nodePlusOne - 1; i != -1 {
|
if i := c.state.nodePlusOne - 1; i != -1 {
|
||||||
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})
|
kind := areaRect
|
||||||
|
if op.Shape == ops.Ellipse {
|
||||||
|
kind = areaEllipse
|
||||||
|
}
|
||||||
|
areaOp := areaOp{kind: kind, rect: frect(op.Bounds)}
|
||||||
|
c.q.areas = append(c.q.areas, areaNode{trans: c.state.t, next: area, area: areaOp})
|
||||||
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,
|
||||||
@@ -123,6 +128,20 @@ func (c *pointerCollector) area(op areaOp) {
|
|||||||
c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1
|
c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// frect converts a rectangle to a f32.Rectangle.
|
||||||
|
func frect(r image.Rectangle) f32.Rectangle {
|
||||||
|
return f32.Rectangle{
|
||||||
|
Min: fpt(r.Min), Max: fpt(r.Max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fpt converts an point to a f32.Point.
|
||||||
|
func fpt(p image.Point) f32.Point {
|
||||||
|
return f32.Point{
|
||||||
|
X: float32(p.X), Y: float32(p.Y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *pointerCollector) popArea() {
|
func (c *pointerCollector) popArea() {
|
||||||
n := len(c.nodeStack)
|
n := len(c.nodeStack)
|
||||||
c.state.nodePlusOne = c.nodeStack[n-1] + 1
|
c.state.nodePlusOne = c.nodeStack[n-1] + 1
|
||||||
@@ -477,26 +496,6 @@ func opDecodeFloat32(d []byte) float32 {
|
|||||||
return float32(int32(binary.LittleEndian.Uint32(d)))
|
return float32(int32(binary.LittleEndian.Uint32(d)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *areaOp) Decode(d []byte) {
|
|
||||||
if ops.OpType(d[0]) != ops.TypeArea {
|
|
||||||
panic("invalid op")
|
|
||||||
}
|
|
||||||
rect := f32.Rectangle{
|
|
||||||
Min: f32.Point{
|
|
||||||
X: opDecodeFloat32(d[2:]),
|
|
||||||
Y: opDecodeFloat32(d[6:]),
|
|
||||||
},
|
|
||||||
Max: f32.Point{
|
|
||||||
X: opDecodeFloat32(d[10:]),
|
|
||||||
Y: opDecodeFloat32(d[14:]),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
*op = areaOp{
|
|
||||||
kind: areaKind(d[1]),
|
|
||||||
rect: rect,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *areaOp) Hit(pos f32.Point) bool {
|
func (op *areaOp) Hit(pos f32.Point) bool {
|
||||||
pos = pos.Sub(op.rect.Min)
|
pos = pos.Sub(op.rect.Min)
|
||||||
size := op.rect.Size()
|
size := op.rect.Size()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPointerWakeup(t *testing.T) {
|
func TestPointerWakeup(t *testing.T) {
|
||||||
@@ -733,6 +734,33 @@ func TestAreaPassthrough(t *testing.T) {
|
|||||||
assertEventSequence(t, r.Events(h), pointer.Cancel, pointer.Press)
|
assertEventSequence(t, r.Events(h), pointer.Cancel, pointer.Press)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEllipse(t *testing.T) {
|
||||||
|
var ops op.Ops
|
||||||
|
|
||||||
|
h := new(int)
|
||||||
|
cl := clip.Ellipse(f32.Rect(0, 0, 100, 100)).Push(&ops)
|
||||||
|
pointer.InputOp{Tag: h, Types: pointer.Press}.Add(&ops)
|
||||||
|
cl.Pop()
|
||||||
|
var r Router
|
||||||
|
r.Frame(&ops)
|
||||||
|
r.Queue(
|
||||||
|
// Outside ellipse.
|
||||||
|
pointer.Event{
|
||||||
|
Position: f32.Pt(10, 10),
|
||||||
|
Type: pointer.Press,
|
||||||
|
},
|
||||||
|
pointer.Event{
|
||||||
|
Type: pointer.Release,
|
||||||
|
},
|
||||||
|
// Inside ellipse.
|
||||||
|
pointer.Event{
|
||||||
|
Position: f32.Pt(50, 50),
|
||||||
|
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) {
|
||||||
|
|||||||
+4
-4
@@ -162,11 +162,11 @@ func (q *Router) collect() {
|
|||||||
pc.load(id)
|
pc.load(id)
|
||||||
|
|
||||||
// Pointer ops.
|
// Pointer ops.
|
||||||
case ops.TypeArea:
|
case ops.TypeClip:
|
||||||
var op areaOp
|
var op ops.ClipOp
|
||||||
op.Decode(encOp.Data)
|
op.Decode(encOp.Data)
|
||||||
pc.area(op)
|
pc.clip(op)
|
||||||
case ops.TypePopArea:
|
case ops.TypePopClip:
|
||||||
pc.popArea()
|
pc.popArea()
|
||||||
case ops.TypePass:
|
case ops.TypePass:
|
||||||
pc.pass()
|
pc.pass()
|
||||||
|
|||||||
+3
-2
@@ -47,7 +47,6 @@ func (p Op) Push(o *op.Ops) Stack {
|
|||||||
|
|
||||||
func (p Op) add(o *op.Ops) {
|
func (p Op) add(o *op.Ops) {
|
||||||
path := p.path
|
path := p.path
|
||||||
outline := p.outline
|
|
||||||
|
|
||||||
bo := binary.LittleEndian
|
bo := binary.LittleEndian
|
||||||
if path.hasSegments {
|
if path.hasSegments {
|
||||||
@@ -77,9 +76,10 @@ func (p Op) add(o *op.Ops) {
|
|||||||
bo.PutUint32(data[5:], uint32(bounds.Min.Y))
|
bo.PutUint32(data[5:], uint32(bounds.Min.Y))
|
||||||
bo.PutUint32(data[9:], uint32(bounds.Max.X))
|
bo.PutUint32(data[9:], uint32(bounds.Max.X))
|
||||||
bo.PutUint32(data[13:], uint32(bounds.Max.Y))
|
bo.PutUint32(data[13:], uint32(bounds.Max.Y))
|
||||||
if outline {
|
if p.outline {
|
||||||
data[17] = byte(1)
|
data[17] = byte(1)
|
||||||
}
|
}
|
||||||
|
data[18] = byte(path.shape)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Stack) Pop() {
|
func (s Stack) Pop() {
|
||||||
@@ -96,6 +96,7 @@ type PathSpec struct {
|
|||||||
// hasSegments tracks whether there are any segments in the path.
|
// hasSegments tracks whether there are any segments in the path.
|
||||||
hasSegments bool
|
hasSegments bool
|
||||||
bounds image.Rectangle
|
bounds image.Rectangle
|
||||||
|
shape ops.Shape
|
||||||
hash uint64
|
hash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Package clip provides operations for clipping paint operations.
|
Package clip provides operations for defining areas that applies to operations
|
||||||
Drawing outside the current clip area is ignored.
|
such as paints and pointer handlers.
|
||||||
|
|
||||||
The current clip is initially the infinite set. Pushing and Op sets the clip
|
The current clip is initially the infinite set. Pushing an Op sets the clip
|
||||||
to the intersection of the current clip and pushed clip area. Popping the
|
to the intersection of the current clip and pushed clip area. Popping the
|
||||||
area restores the clip to its state before pushing.
|
area restores the clip to its state before pushing.
|
||||||
|
|
||||||
|
|||||||
+15
-12
@@ -7,6 +7,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/internal/ops"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ func (r Rect) Op() Op {
|
|||||||
return Op{
|
return Op{
|
||||||
outline: true,
|
outline: true,
|
||||||
path: PathSpec{
|
path: PathSpec{
|
||||||
|
shape: ops.Rect,
|
||||||
bounds: image.Rectangle(r),
|
bounds: image.Rectangle(r),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -132,18 +134,16 @@ func (c Circle) Path(ops *op.Ops) PathSpec {
|
|||||||
Min: f32.Pt(c.Center.X-c.Radius, c.Center.Y-c.Radius),
|
Min: f32.Pt(c.Center.X-c.Radius, c.Center.Y-c.Radius),
|
||||||
Max: f32.Pt(c.Center.X+c.Radius, c.Center.Y+c.Radius),
|
Max: f32.Pt(c.Center.X+c.Radius, c.Center.Y+c.Radius),
|
||||||
}
|
}
|
||||||
return Ellipse{b}.Path(ops)
|
return Ellipse(b).path(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ellipse represents the largest axis-aligned ellipse that
|
// Ellipse represents the largest axis-aligned ellipse that
|
||||||
// is contained in its bounds.
|
// is contained in its bounds.
|
||||||
type Ellipse struct {
|
type Ellipse f32.Rectangle
|
||||||
Bounds f32.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
// Op returns the op for the filled ellipse.
|
// Op returns the op for the filled ellipse.
|
||||||
func (e Ellipse) Op(ops *op.Ops) Op {
|
func (e Ellipse) Op(ops *op.Ops) Op {
|
||||||
return Outline{Path: e.Path(ops)}.Op()
|
return Outline{Path: e.path(ops)}.Op()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the filled ellipse clip op on the clip stack.
|
// Push the filled ellipse clip op on the clip stack.
|
||||||
@@ -151,17 +151,18 @@ func (e Ellipse) Push(ops *op.Ops) Stack {
|
|||||||
return e.Op(ops).Push(ops)
|
return e.Op(ops).Push(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path constructs a path for the ellipse.
|
// path constructs a path for the ellipse.
|
||||||
func (e Ellipse) Path(ops *op.Ops) PathSpec {
|
func (e Ellipse) path(o *op.Ops) PathSpec {
|
||||||
var p Path
|
var p Path
|
||||||
p.Begin(ops)
|
p.Begin(o)
|
||||||
|
|
||||||
center := e.Bounds.Max.Add(e.Bounds.Min).Mul(.5)
|
bounds := f32.Rectangle(e)
|
||||||
diam := e.Bounds.Dx()
|
center := bounds.Max.Add(bounds.Min).Mul(.5)
|
||||||
|
diam := bounds.Dx()
|
||||||
r := diam * .5
|
r := diam * .5
|
||||||
// We'll model the ellipse as a circle scaled in the Y
|
// We'll model the ellipse as a circle scaled in the Y
|
||||||
// direction.
|
// direction.
|
||||||
scale := e.Bounds.Dy() / diam
|
scale := bounds.Dy() / diam
|
||||||
|
|
||||||
// https://pomax.github.io/bezierinfo/#circles_cubic.
|
// https://pomax.github.io/bezierinfo/#circles_cubic.
|
||||||
const q = 4 * (math.Sqrt2 - 1) / 3
|
const q = 4 * (math.Sqrt2 - 1) / 3
|
||||||
@@ -190,7 +191,9 @@ func (e Ellipse) Path(ops *op.Ops) PathSpec {
|
|||||||
f32.Point{X: center.X - curve, Y: center.Y - r*scale},
|
f32.Point{X: center.X - curve, Y: center.Y - r*scale},
|
||||||
top,
|
top,
|
||||||
)
|
)
|
||||||
return p.End()
|
ellipse := p.End()
|
||||||
|
ellipse.shape = ops.Ellipse
|
||||||
|
return ellipse
|
||||||
}
|
}
|
||||||
|
|
||||||
func fPt(p image.Point) f32.Point {
|
func fPt(p image.Point) f32.Point {
|
||||||
|
|||||||
+3
-2
@@ -3,8 +3,9 @@
|
|||||||
/*
|
/*
|
||||||
Package paint provides drawing operations for 2D graphics.
|
Package paint provides drawing operations for 2D graphics.
|
||||||
|
|
||||||
The PaintOp operation fills the current clip with the current brush,
|
The PaintOp operation fills the current clip with the current brush, taking the
|
||||||
taking the current transformation into account.
|
current transformation into account. Drawing outside the current clip area is
|
||||||
|
ignored.
|
||||||
|
|
||||||
The current brush is set by either a ColorOp for a constant color, or
|
The current brush is set by either a ColorOp for a constant color, or
|
||||||
ImageOp for an image, or LinearGradientOp for gradients.
|
ImageOp for an image, or LinearGradientOp for gradients.
|
||||||
|
|||||||
+3
-3
@@ -92,19 +92,19 @@ func (s *Scrollbar) Layout(gtx layout.Context, axis layout.Axis, viewportStart,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddTrack configures the track click listener for the scrollbar to use
|
// AddTrack configures the track click listener for the scrollbar to use
|
||||||
// the most recently added pointer.AreaOp.
|
// the current clip area.
|
||||||
func (s *Scrollbar) AddTrack(ops *op.Ops) {
|
func (s *Scrollbar) AddTrack(ops *op.Ops) {
|
||||||
s.track.Add(ops)
|
s.track.Add(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddIndicator configures the indicator click listener for the scrollbar to use
|
// AddIndicator configures the indicator click listener for the scrollbar to use
|
||||||
// the most recently added pointer.AreaOp.
|
// the current clip area.
|
||||||
func (s *Scrollbar) AddIndicator(ops *op.Ops) {
|
func (s *Scrollbar) AddIndicator(ops *op.Ops) {
|
||||||
s.indicator.Add(ops)
|
s.indicator.Add(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddDrag configures the drag listener for the scrollbar to use
|
// AddDrag configures the drag listener for the scrollbar to use
|
||||||
// the most recently added pointer.AreaOp.
|
// the current clip area.
|
||||||
func (s *Scrollbar) AddDrag(ops *op.Ops) {
|
func (s *Scrollbar) AddDrag(ops *op.Ops) {
|
||||||
s.drag.Add(ops)
|
s.drag.Add(ops)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user