forked from joejulian/gio
all: [API] split operation stack into per-state stacks
The op.Save and Load methods exist to support the need for
transformation, clip, pointer area state to behave as stacks. For
example, layout needs to apply an offset to its children but not
subsequent operations.
Before this change, op.Save and Load were used to save and restore the
state:
ops := new(op.Ops)
// Save state.
state := op.Save(ops)
// Apply offset.
op.Offset(...).Add(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
state.Load()
A drawback with the op.Save mechanism is that there is no direct
connection between the state change and the saving and loading of state.
This causes confusion as to when a Save/Load is needed and who is
responsible for performing them, which leads to subtle bugs and over-use
of Save/Loads.
This change gets rid of the general state stack and replaces it with
per-state stacks. There is now a stack for transformation, clip, pointer
areas, and they can only be restored by the code pushing state to them.
The example above now becomes:
ops := new(op.Ops)
// Push offset to the transformation stack.
stack := op.Offset(...).Push(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
stack.Pop()
For convenience, transformation also be Add'ed if the stack operation is
not required.
Simple state such as the current material no longer has a way to be
restored; it is assumed the client of a PaintOp adds their desired
material operation before it.
API change: replace op.Save/Load with explicit Push/Pop scopes for
op.TransformOps, pointer.AreaOps, clip.Ops.
To ease porting, this change retains a version of op.Save/Load that
saves and restores the transformation and clip stacks. It also retains
an Add method for clip.Op.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -189,8 +189,8 @@ func shapeRune(f text.Face, r rune) (clip.Op, error) {
|
||||
// areShapesEqual returns true iff both given text shapes are produced with identical operations.
|
||||
func areShapesEqual(shape1, shape2 clip.Op) bool {
|
||||
var ops1, ops2 op.Ops
|
||||
shape1.Add(&ops1)
|
||||
shape2.Add(&ops2)
|
||||
shape1.Push(&ops1).Pop()
|
||||
shape2.Push(&ops2).Pop()
|
||||
var r1, r2 ops.Reader
|
||||
r1.Reset(&ops1)
|
||||
r2.Reset(&ops2)
|
||||
|
||||
+49
-31
@@ -179,15 +179,21 @@ type collector struct {
|
||||
hasher maphash.Hash
|
||||
profile bool
|
||||
reader ops.Reader
|
||||
states []encoderState
|
||||
states []f32.Affine2D
|
||||
clear bool
|
||||
clearColor f32color.RGBA
|
||||
clipStates []clipState
|
||||
order []hashIndex
|
||||
transStack []transEntry
|
||||
prevFrame opsCollector
|
||||
frame opsCollector
|
||||
}
|
||||
|
||||
type transEntry struct {
|
||||
t f32.Affine2D
|
||||
relTrans f32.Affine2D
|
||||
}
|
||||
|
||||
type hashIndex struct {
|
||||
index int
|
||||
hash uint64
|
||||
@@ -260,6 +266,7 @@ type clipState struct {
|
||||
path []byte
|
||||
pathKey ops.Key
|
||||
intersect f32.Rectangle
|
||||
push bool
|
||||
|
||||
clipKey
|
||||
}
|
||||
@@ -1666,6 +1673,7 @@ func (c *collector) reset() {
|
||||
c.prevFrame, c.frame = c.frame, c.prevFrame
|
||||
c.profile = false
|
||||
c.clipStates = c.clipStates[:0]
|
||||
c.transStack = c.transStack[:0]
|
||||
c.frame.reset()
|
||||
}
|
||||
|
||||
@@ -1676,9 +1684,9 @@ func (c *opsCollector) reset() {
|
||||
c.layers = c.layers[:0]
|
||||
}
|
||||
|
||||
func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, key ops.Key, hash uint64, stroke clip.StrokeStyle) {
|
||||
func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, key ops.Key, hash uint64, stroke clip.StrokeStyle, push bool) {
|
||||
// Rectangle clip regions.
|
||||
if len(path) == 0 {
|
||||
if len(path) == 0 && !push {
|
||||
// If the rectangular clip region contains a previous path it can be discarded.
|
||||
p := state.clip
|
||||
t := state.relTrans.Invert()
|
||||
@@ -1704,6 +1712,7 @@ func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle,
|
||||
path: path,
|
||||
pathKey: key,
|
||||
intersect: intersect,
|
||||
push: push,
|
||||
clipKey: clipKey{
|
||||
bounds: bounds,
|
||||
relTrans: state.relTrans,
|
||||
@@ -1718,11 +1727,15 @@ func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle,
|
||||
func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textureOp) {
|
||||
fview := f32.Rectangle{Max: layout.FPt(viewport)}
|
||||
c.reader.Reset(root)
|
||||
state := encoderState{
|
||||
paintKey: paintKey{
|
||||
color: color.NRGBA{A: 0xff},
|
||||
},
|
||||
var state encoderState
|
||||
reset := func() {
|
||||
state = encoderState{
|
||||
paintKey: paintKey{
|
||||
color: color.NRGBA{A: 0xff},
|
||||
},
|
||||
}
|
||||
}
|
||||
reset()
|
||||
r := &c.reader
|
||||
var (
|
||||
pathData struct {
|
||||
@@ -1732,16 +1745,24 @@ func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textur
|
||||
}
|
||||
str clip.StrokeStyle
|
||||
)
|
||||
c.save(opconst.InitialStateID, state)
|
||||
c.addClip(&state, fview, fview, nil, ops.Key{}, 0, clip.StrokeStyle{})
|
||||
c.addClip(&state, fview, fview, nil, ops.Key{}, 0, clip.StrokeStyle{}, false)
|
||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypeProfile:
|
||||
c.profile = true
|
||||
case opconst.TypeTransform:
|
||||
dop := ops.DecodeTransform(encOp.Data)
|
||||
dop, push := ops.DecodeTransform(encOp.Data)
|
||||
if push {
|
||||
c.transStack = append(c.transStack, transEntry{t: state.t, relTrans: state.relTrans})
|
||||
}
|
||||
state.t = state.t.Mul(dop)
|
||||
state.relTrans = state.relTrans.Mul(dop)
|
||||
case opconst.TypePopTransform:
|
||||
n := len(c.transStack)
|
||||
st := c.transStack[n-1]
|
||||
c.transStack = c.transStack[:n-1]
|
||||
state.t = st.t
|
||||
state.relTrans = st.relTrans
|
||||
case opconst.TypeStroke:
|
||||
str = decodeStrokeOp(encOp.Data)
|
||||
case opconst.TypePath:
|
||||
@@ -1756,9 +1777,18 @@ func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textur
|
||||
case opconst.TypeClip:
|
||||
var op clipOp
|
||||
op.decode(encOp.Data)
|
||||
c.addClip(&state, fview, op.bounds, pathData.data, pathData.key, pathData.hash, str)
|
||||
c.addClip(&state, fview, op.bounds, pathData.data, pathData.key, pathData.hash, str, op.push)
|
||||
pathData.data = nil
|
||||
str = clip.StrokeStyle{}
|
||||
case opconst.TypePopClip:
|
||||
for {
|
||||
push := state.clip.push
|
||||
state.relTrans = state.clip.relTrans.Mul(state.relTrans)
|
||||
state.clip = state.clip.parent
|
||||
if push {
|
||||
break
|
||||
}
|
||||
}
|
||||
case opconst.TypeColor:
|
||||
state.matType = materialColor
|
||||
state.color = decodeColorOp(encOp.Data)
|
||||
@@ -1777,7 +1807,7 @@ func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textur
|
||||
if paintState.matType == materialTexture {
|
||||
// Clip to the bounds of the image, to hide other images in the atlas.
|
||||
bounds := paintState.image.src.Bounds()
|
||||
c.addClip(&paintState, fview, layout.FRect(bounds), nil, ops.Key{}, 0, clip.StrokeStyle{})
|
||||
c.addClip(&paintState, fview, layout.FRect(bounds), nil, ops.Key{}, 0, clip.StrokeStyle{}, false)
|
||||
}
|
||||
intersect := paintState.clip.intersect
|
||||
if intersect.Empty() {
|
||||
@@ -1818,24 +1848,12 @@ func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textur
|
||||
})
|
||||
case opconst.TypeSave:
|
||||
id := ops.DecodeSave(encOp.Data)
|
||||
c.save(id, state)
|
||||
c.save(id, state.t)
|
||||
case opconst.TypeLoad:
|
||||
id, mask := ops.DecodeLoad(encOp.Data)
|
||||
s := c.states[id]
|
||||
if mask&^opconst.TransformState != 0 {
|
||||
state = s
|
||||
} else if mask&opconst.TransformState != 0 {
|
||||
state.t = s.t
|
||||
state.relTrans = s.t
|
||||
if cl := state.clip; cl != nil {
|
||||
var relTrans f32.Affine2D
|
||||
for cl != nil {
|
||||
relTrans = cl.relTrans.Mul(relTrans)
|
||||
cl = cl.parent
|
||||
}
|
||||
state.relTrans = relTrans.Invert().Mul(state.relTrans)
|
||||
}
|
||||
}
|
||||
reset()
|
||||
id := ops.DecodeLoad(encOp.Data)
|
||||
state.t = c.states[id]
|
||||
state.relTrans = state.t
|
||||
}
|
||||
}
|
||||
for i := range c.frame.ops {
|
||||
@@ -2132,9 +2150,9 @@ func encodeOp(viewport image.Point, absOff image.Point, enc *encoder, texOps []t
|
||||
}
|
||||
}
|
||||
|
||||
func (c *collector) save(id int, state encoderState) {
|
||||
func (c *collector) save(id int, state f32.Affine2D) {
|
||||
if extra := id - len(c.states) + 1; extra > 0 {
|
||||
c.states = append(c.states, make([]encoderState, extra)...)
|
||||
c.states = append(c.states, make([]f32.Affine2D, extra)...)
|
||||
}
|
||||
c.states[id] = state
|
||||
}
|
||||
|
||||
+46
-26
@@ -75,7 +75,8 @@ type renderer struct {
|
||||
type drawOps struct {
|
||||
profile bool
|
||||
reader ops.Reader
|
||||
states []drawState
|
||||
states []f32.Affine2D
|
||||
transStack []f32.Affine2D
|
||||
cache *resourceCache
|
||||
vertCache []byte
|
||||
viewport image.Point
|
||||
@@ -110,6 +111,9 @@ type pathOp struct {
|
||||
// rect tracks whether the clip stack can be represented by a
|
||||
// pixel-aligned rectangle.
|
||||
rect bool
|
||||
// push is set to true for clip operations that corresponds to
|
||||
// a push operation.
|
||||
push bool
|
||||
// clip is the union of all
|
||||
// later clip rectangles.
|
||||
clip image.Rectangle
|
||||
@@ -171,6 +175,7 @@ type clipOp struct {
|
||||
// TODO: Use image.Rectangle?
|
||||
bounds f32.Rectangle
|
||||
outline bool
|
||||
push bool
|
||||
}
|
||||
|
||||
// imageOpData is the shadow of paint.ImageOp.
|
||||
@@ -204,6 +209,7 @@ func (op *clipOp) decode(data []byte) {
|
||||
*op = clipOp{
|
||||
bounds: layout.FRect(r),
|
||||
outline: data[17] == 1,
|
||||
push: data[18] == 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,6 +828,7 @@ func (d *drawOps) reset(cache *resourceCache, viewport image.Point) {
|
||||
d.pathOps = d.pathOps[:0]
|
||||
d.pathOpCache = d.pathOpCache[:0]
|
||||
d.vertCache = d.vertCache[:0]
|
||||
d.transStack = d.transStack[:0]
|
||||
}
|
||||
|
||||
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
||||
@@ -829,10 +836,7 @@ func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
||||
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
|
||||
}
|
||||
d.reader.Reset(root)
|
||||
state := drawState{
|
||||
color: color.NRGBA{A: 0xff},
|
||||
}
|
||||
d.collectOps(&d.reader, viewf, state)
|
||||
d.collectOps(&d.reader, viewf)
|
||||
}
|
||||
|
||||
func (d *drawOps) buildPaths(ctx driver.Device) {
|
||||
@@ -853,7 +857,7 @@ func (d *drawOps) newPathOp() *pathOp {
|
||||
return &d.pathOpCache[len(d.pathOpCache)-1]
|
||||
}
|
||||
|
||||
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point) {
|
||||
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
|
||||
npath := d.newPathOp()
|
||||
*npath = pathOp{
|
||||
parent: state.cpath,
|
||||
@@ -861,6 +865,7 @@ func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds
|
||||
off: off,
|
||||
intersect: bounds.Add(off),
|
||||
rect: true,
|
||||
push: push,
|
||||
}
|
||||
if npath.parent != nil {
|
||||
npath.rect = npath.parent.rect
|
||||
@@ -885,9 +890,9 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *drawOps) save(id int, state drawState) {
|
||||
func (d *drawOps) save(id int, state f32.Affine2D) {
|
||||
if extra := id - len(d.states) + 1; extra > 0 {
|
||||
d.states = append(d.states, make([]drawState, extra)...)
|
||||
d.states = append(d.states, make([]f32.Affine2D, extra)...)
|
||||
}
|
||||
d.states[id] = state
|
||||
}
|
||||
@@ -901,20 +906,33 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
|
||||
return k
|
||||
}
|
||||
|
||||
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle, state drawState) {
|
||||
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
||||
var (
|
||||
quads quadsOp
|
||||
str clip.StrokeStyle
|
||||
state drawState
|
||||
)
|
||||
d.save(opconst.InitialStateID, state)
|
||||
reset := func() {
|
||||
state = drawState{
|
||||
color: color.NRGBA{A: 0xff},
|
||||
}
|
||||
}
|
||||
reset()
|
||||
loop:
|
||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypeProfile:
|
||||
d.profile = true
|
||||
case opconst.TypeTransform:
|
||||
dop := ops.DecodeTransform(encOp.Data)
|
||||
dop, push := ops.DecodeTransform(encOp.Data)
|
||||
if push {
|
||||
d.transStack = append(d.transStack, state.t)
|
||||
}
|
||||
state.t = state.t.Mul(dop)
|
||||
case opconst.TypePopTransform:
|
||||
n := len(d.transStack)
|
||||
state.t = d.transStack[n-1]
|
||||
d.transStack = d.transStack[:n-1]
|
||||
|
||||
case opconst.TypeStroke:
|
||||
str = decodeStrokeOp(encOp.Data)
|
||||
@@ -954,11 +972,18 @@ loop:
|
||||
} else {
|
||||
quads.aux, op.bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
||||
quads.key = opKey{Key: encOp.Key}
|
||||
quads.key.SetTransform(trans) // TODO: This call has no effect.
|
||||
}
|
||||
d.addClipPath(&state, quads.aux, quads.key, op.bounds, off)
|
||||
d.addClipPath(&state, quads.aux, quads.key, op.bounds, off, op.push)
|
||||
quads = quadsOp{}
|
||||
str = clip.StrokeStyle{}
|
||||
case opconst.TypePopClip:
|
||||
for {
|
||||
push := state.cpath.push
|
||||
state.cpath = state.cpath.parent
|
||||
if push {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case opconst.TypeColor:
|
||||
state.matType = materialColor
|
||||
@@ -977,7 +1002,7 @@ loop:
|
||||
// Transform (if needed) the painting rectangle and if so generate a clip path,
|
||||
// for those cases also compute a partialTrans that maps texture coordinates between
|
||||
// the new bounding rectangle and the transformed original paint rectangle.
|
||||
trans, off := splitTransform(state.t)
|
||||
t, off := splitTransform(state.t)
|
||||
// Fill the clip area, unless the material is a (bounded) image.
|
||||
// TODO: Find a tighter bound.
|
||||
inf := float32(1e6)
|
||||
@@ -985,7 +1010,7 @@ loop:
|
||||
if state.matType == materialTexture {
|
||||
dst = layout.FRect(state.image.src.Rect)
|
||||
}
|
||||
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, trans)
|
||||
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
|
||||
cl := viewport.Intersect(bnd.Add(off))
|
||||
if state.cpath != nil {
|
||||
cl = state.cpath.intersect.Intersect(cl)
|
||||
@@ -998,8 +1023,8 @@ loop:
|
||||
// The paint operation is sheared or rotated, add a clip path representing
|
||||
// this transformed rectangle.
|
||||
k := opKey{Key: encOp.Key}
|
||||
k.SetTransform(trans) // TODO: This call has no effect.
|
||||
d.addClipPath(&state, clipData, k, bnd, off)
|
||||
k.SetTransform(t) // TODO: This call has no effect.
|
||||
d.addClipPath(&state, clipData, k, bnd, off, false)
|
||||
}
|
||||
|
||||
bounds := boundRectF(cl)
|
||||
@@ -1027,16 +1052,11 @@ loop:
|
||||
}
|
||||
case opconst.TypeSave:
|
||||
id := ops.DecodeSave(encOp.Data)
|
||||
d.save(id, state)
|
||||
d.save(id, state.t)
|
||||
case opconst.TypeLoad:
|
||||
id, mask := ops.DecodeLoad(encOp.Data)
|
||||
s := d.states[id]
|
||||
if mask&opconst.TransformState != 0 {
|
||||
state.t = s.t
|
||||
}
|
||||
if mask&^opconst.TransformState != 0 {
|
||||
state = s
|
||||
}
|
||||
reset()
|
||||
id := ops.DecodeLoad(encOp.Data)
|
||||
state.t = d.states[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestClipping(t *testing.T) {
|
||||
Max: f32.Point{X: 250, Y: 250},
|
||||
},
|
||||
SE: 75,
|
||||
}.Add(&ops)
|
||||
}.Push(&ops)
|
||||
paint.PaintOp{}.Add(&ops)
|
||||
paint.ColorOp{Color: col2}.Add(&ops)
|
||||
clip.RRect{
|
||||
@@ -63,7 +63,7 @@ func TestClipping(t *testing.T) {
|
||||
Max: f32.Point{X: 350, Y: 350},
|
||||
},
|
||||
NW: 75,
|
||||
}.Add(&ops)
|
||||
}.Push(&ops)
|
||||
paint.PaintOp{}.Add(&ops)
|
||||
if err := w.Frame(&ops); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -84,13 +84,12 @@ func BenchmarkDrawUI(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
resetOps(gtx)
|
||||
|
||||
p := op.Save(gtx.Ops)
|
||||
off := float32(math.Mod(float64(i)/10, 10))
|
||||
op.Offset(f32.Pt(off, off)).Add(gtx.Ops)
|
||||
t := op.Offset(f32.Pt(off, off)).Push(gtx.Ops)
|
||||
|
||||
drawCore(gtx, th)
|
||||
|
||||
p.Load()
|
||||
t.Pop()
|
||||
w.Frame(gtx.Ops)
|
||||
}
|
||||
finishBenchmark(b, w)
|
||||
@@ -107,14 +106,13 @@ func BenchmarkDrawUITransformed(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
resetOps(gtx)
|
||||
|
||||
p := op.Save(gtx.Ops)
|
||||
angle := float32(math.Mod(float64(i)/1000, 0.05))
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
|
||||
op.Affine(a).Add(gtx.Ops)
|
||||
t := op.Affine(a).Push(gtx.Ops)
|
||||
|
||||
drawCore(gtx, th)
|
||||
|
||||
p.Load()
|
||||
t.Pop()
|
||||
w.Frame(gtx.Ops)
|
||||
}
|
||||
finishBenchmark(b, w)
|
||||
@@ -158,7 +156,6 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
||||
func draw1000Circles(gtx layout.Context) {
|
||||
ops := gtx.Ops
|
||||
for x := 0; x < 100; x++ {
|
||||
p := op.Save(ops)
|
||||
op.Offset(f32.Pt(float32(x*10), 0)).Add(ops)
|
||||
for y := 0; y < 10; y++ {
|
||||
paint.FillShape(ops,
|
||||
@@ -167,7 +164,6 @@ func draw1000Circles(gtx layout.Context) {
|
||||
)
|
||||
op.Offset(f32.Pt(0, float32(100))).Add(ops)
|
||||
}
|
||||
p.Load()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,21 +171,18 @@ func draw1000CirclesInstanced(gtx layout.Context) {
|
||||
ops := gtx.Ops
|
||||
|
||||
r := op.Record(ops)
|
||||
clip.RRect{Rect: f32.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Add(ops)
|
||||
cl := clip.RRect{Rect: f32.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
cl.Pop()
|
||||
c := r.Stop()
|
||||
|
||||
for x := 0; x < 100; x++ {
|
||||
p := op.Save(ops)
|
||||
op.Offset(f32.Pt(float32(x*10), 0)).Add(ops)
|
||||
for y := 0; y < 10; y++ {
|
||||
pi := op.Save(ops)
|
||||
paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
|
||||
c.Add(ops)
|
||||
pi.Load()
|
||||
op.Offset(f32.Pt(0, float32(100))).Add(ops)
|
||||
}
|
||||
p.Load()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +202,6 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
|
||||
ops := &op1
|
||||
c := op.Record(ops)
|
||||
for x := 0; x < 9; x++ {
|
||||
p := op.Save(ops)
|
||||
op.Offset(f32.Pt(float32(x*50), 0)).Add(ops)
|
||||
for y := 0; y < 9; y++ {
|
||||
paint.FillShape(ops,
|
||||
@@ -218,7 +210,6 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
|
||||
)
|
||||
op.Offset(f32.Pt(0, float32(50))).Add(ops)
|
||||
}
|
||||
p.Load()
|
||||
}
|
||||
c1 <- c.Stop()
|
||||
}()
|
||||
@@ -232,18 +223,18 @@ func drawShapeInstances(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
||||
co := op.Record(ops)
|
||||
|
||||
r := op.Record(ops)
|
||||
clip.RRect{Rect: f32.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Add(ops)
|
||||
cl := clip.RRect{Rect: f32.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
cl.Pop()
|
||||
c := r.Stop()
|
||||
|
||||
squares.Add(ops)
|
||||
rad := float32(0)
|
||||
for x := 0; x < 20; x++ {
|
||||
for y := 0; y < 20; y++ {
|
||||
p := op.Save(ops)
|
||||
op.Offset(f32.Pt(float32(x*50+25), float32(y*50+25))).Add(ops)
|
||||
t := op.Offset(f32.Pt(float32(x*50+25), float32(y*50+25))).Push(ops)
|
||||
c.Add(ops)
|
||||
p.Load()
|
||||
t.Pop()
|
||||
rad += math.Pi * 2 / 400
|
||||
}
|
||||
}
|
||||
@@ -261,11 +252,10 @@ func drawText(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
||||
txt := material.H6(th, "")
|
||||
for x := 0; x < 40; x++ {
|
||||
txt.Text = textRows[x]
|
||||
p := op.Save(ops)
|
||||
op.Offset(f32.Pt(float32(0), float32(24*x))).Add(ops)
|
||||
t := op.Offset(f32.Pt(float32(0), float32(24*x))).Push(ops)
|
||||
gtx.Ops = ops
|
||||
txt.Layout(gtx)
|
||||
p.Load()
|
||||
t.Pop()
|
||||
}
|
||||
c3 <- c.Stop()
|
||||
}()
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestPaintRect(t *testing.T) {
|
||||
|
||||
func TestPaintClippedRect(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
clip.RRect{Rect: f32.Rect(25, 25, 60, 60)}.Add(o)
|
||||
defer clip.RRect{Rect: f32.Rect(25, 25, 60, 60)}.Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
@@ -42,8 +42,8 @@ func TestPaintClippedRect(t *testing.T) {
|
||||
func TestPaintClippedCircle(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
r := float32(10)
|
||||
clip.RRect{Rect: f32.Rect(20, 20, 40, 40), SE: r, SW: r, NW: r, NE: r}.Add(o)
|
||||
clip.Rect(image.Rect(0, 0, 30, 50)).Add(o)
|
||||
defer clip.RRect{Rect: f32.Rect(20, 20, 40, 40), SE: r, SW: r, NW: r, NE: r}.Push(o).Pop()
|
||||
defer clip.Rect(image.Rect(0, 0, 30, 50)).Push(o).Pop()
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
r.expect(21, 21, transparent)
|
||||
@@ -72,9 +72,9 @@ func TestPaintArc(t *testing.T) {
|
||||
p.Line(f32.Pt(0, -10))
|
||||
p.Line(f32.Pt(-50, 0))
|
||||
p.Close()
|
||||
clip.Outline{
|
||||
defer clip.Outline{
|
||||
Path: p.End(),
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
|
||||
}, func(r result) {
|
||||
@@ -94,9 +94,9 @@ func TestPaintAbsolute(t *testing.T) {
|
||||
p.LineTo(f32.Pt(80, 20))
|
||||
p.QuadTo(f32.Pt(80, 80), f32.Pt(20, 80))
|
||||
p.Close()
|
||||
clip.Outline{
|
||||
defer clip.Outline{
|
||||
Path: p.End(),
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
|
||||
}, func(r result) {
|
||||
@@ -110,7 +110,7 @@ func TestPaintAbsolute(t *testing.T) {
|
||||
func TestPaintTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
squares.Add(o)
|
||||
scale(80.0/512, 80.0/512).Add(o)
|
||||
defer scale(80.0/512, 80.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, colornames.Blue)
|
||||
@@ -123,15 +123,15 @@ func TestPaintTexture(t *testing.T) {
|
||||
func TestTexturedStrokeClipped(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
smallSquares.Add(o)
|
||||
op.Offset(f32.Pt(50, 50)).Add(o)
|
||||
clip.Stroke{
|
||||
defer op.Offset(f32.Pt(50, 50)).Push(o).Pop()
|
||||
defer clip.Stroke{
|
||||
Path: clip.RRect{Rect: f32.Rect(0, 0, 30, 30)}.Path(o),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
clip.RRect{Rect: f32.Rect(-30, -30, 60, 60)}.Add(o)
|
||||
op.Offset(f32.Pt(-10, -10)).Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
defer clip.RRect{Rect: f32.Rect(-30, -30, 60, 60)}.Push(o).Pop()
|
||||
defer op.Offset(f32.Pt(-10, -10)).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
@@ -140,14 +140,14 @@ func TestTexturedStrokeClipped(t *testing.T) {
|
||||
func TestTexturedStroke(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
smallSquares.Add(o)
|
||||
op.Offset(f32.Pt(50, 50)).Add(o)
|
||||
clip.Stroke{
|
||||
defer op.Offset(f32.Pt(50, 50)).Push(o).Pop()
|
||||
defer clip.Stroke{
|
||||
Path: clip.RRect{Rect: f32.Rect(0, 0, 30, 30)}.Path(o),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
op.Offset(f32.Pt(-10, -10)).Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
defer op.Offset(f32.Pt(-10, -10)).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
@@ -156,8 +156,8 @@ func TestTexturedStroke(t *testing.T) {
|
||||
func TestPaintClippedTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
squares.Add(o)
|
||||
clip.RRect{Rect: f32.Rect(0, 0, 40, 40)}.Add(o)
|
||||
scale(80.0/512, 80.0/512).Add(o)
|
||||
defer clip.RRect{Rect: f32.Rect(0, 0, 40, 40)}.Push(o).Pop()
|
||||
defer scale(80.0/512, 80.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
r.expect(40, 40, transparent)
|
||||
@@ -168,14 +168,14 @@ func TestPaintClippedTexture(t *testing.T) {
|
||||
func TestStrokedPathBevelFlat(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
p := newStrokedPath(o)
|
||||
clip.Stroke{
|
||||
defer clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2.5,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
@@ -187,14 +187,14 @@ func TestStrokedPathBevelFlat(t *testing.T) {
|
||||
func TestStrokedPathBevelRound(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
p := newStrokedPath(o)
|
||||
clip.Stroke{
|
||||
defer clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2.5,
|
||||
Cap: clip.RoundCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
@@ -206,14 +206,14 @@ func TestStrokedPathBevelRound(t *testing.T) {
|
||||
func TestStrokedPathBevelSquare(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
p := newStrokedPath(o)
|
||||
clip.Stroke{
|
||||
defer clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2.5,
|
||||
Cap: clip.SquareCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
@@ -225,14 +225,14 @@ func TestStrokedPathBevelSquare(t *testing.T) {
|
||||
func TestStrokedPathRoundRound(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
p := newStrokedPath(o)
|
||||
clip.Stroke{
|
||||
defer clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2.5,
|
||||
Cap: clip.RoundCap,
|
||||
Join: clip.RoundJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
@@ -244,9 +244,8 @@ func TestStrokedPathRoundRound(t *testing.T) {
|
||||
func TestStrokedPathFlatMiter(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newZigZagPath(o)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
@@ -254,23 +253,22 @@ func TestStrokedPathFlatMiter(t *testing.T) {
|
||||
Join: clip.BevelJoin,
|
||||
Miter: 5,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newZigZagPath(o)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
}, func(r result) {
|
||||
@@ -283,9 +281,8 @@ func TestStrokedPathFlatMiter(t *testing.T) {
|
||||
func TestStrokedPathFlatMiterInf(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newZigZagPath(o)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
@@ -293,25 +290,23 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
|
||||
Join: clip.BevelJoin,
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newZigZagPath(o)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(40, 10, colornames.Black)
|
||||
@@ -322,36 +317,34 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
|
||||
func TestStrokedPathZeroWidth(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := new(clip.Path)
|
||||
p.Begin(o)
|
||||
p.Move(f32.Pt(10, 50))
|
||||
p.Line(f32.Pt(50, 0))
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p.End(),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
|
||||
paint.Fill(o, black)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := new(clip.Path)
|
||||
p.Begin(o)
|
||||
p.Move(f32.Pt(10, 50))
|
||||
p.Line(f32.Pt(30, 0))
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p.End(),
|
||||
}.Op().Add(o) // width=0, disable stroke
|
||||
}.Op().Push(o) // width=0, disable stroke
|
||||
|
||||
paint.Fill(o, red)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
}, func(r result) {
|
||||
@@ -365,7 +358,6 @@ func TestStrokedPathZeroWidth(t *testing.T) {
|
||||
func TestDashedPathFlatCapEllipse(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newEllipsePath(o)
|
||||
|
||||
var dash clip.Dash
|
||||
@@ -373,7 +365,7 @@ func TestDashedPathFlatCapEllipse(t *testing.T) {
|
||||
dash.Dash(5)
|
||||
dash.Dash(3)
|
||||
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
@@ -382,29 +374,28 @@ func TestDashedPathFlatCapEllipse(t *testing.T) {
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
Dashes: dash.End(),
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
|
||||
paint.Fill(
|
||||
o,
|
||||
red,
|
||||
)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newEllipsePath(o)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
|
||||
paint.Fill(
|
||||
o,
|
||||
black,
|
||||
)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
}, func(r result) {
|
||||
@@ -417,14 +408,13 @@ func TestDashedPathFlatCapEllipse(t *testing.T) {
|
||||
func TestDashedPathFlatCapZ(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newZigZagPath(o)
|
||||
var dash clip.Dash
|
||||
dash.Begin(o)
|
||||
dash.Dash(5)
|
||||
dash.Dash(3)
|
||||
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
@@ -433,24 +423,23 @@ func TestDashedPathFlatCapZ(t *testing.T) {
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
Dashes: dash.End(),
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newZigZagPath(o)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
@@ -463,13 +452,12 @@ func TestDashedPathFlatCapZ(t *testing.T) {
|
||||
func TestDashedPathFlatCapZNoDash(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newZigZagPath(o)
|
||||
var dash clip.Dash
|
||||
dash.Begin(o)
|
||||
dash.Phase(1)
|
||||
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
@@ -478,22 +466,21 @@ func TestDashedPathFlatCapZNoDash(t *testing.T) {
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
Dashes: dash.End(),
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
stk := op.Save(o)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: newZigZagPath(o),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
@@ -506,11 +493,10 @@ func TestDashedPathFlatCapZNoDash(t *testing.T) {
|
||||
func TestDashedPathFlatCapZNoPath(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
stk := op.Save(o)
|
||||
var dash clip.Dash
|
||||
dash.Begin(o)
|
||||
dash.Dash(0)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: newZigZagPath(o),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
@@ -519,23 +505,22 @@ func TestDashedPathFlatCapZNoPath(t *testing.T) {
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
Dashes: dash.End(),
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
stk := op.Save(o)
|
||||
p := newZigZagPath(o)
|
||||
clip.Stroke{
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
stk.Load()
|
||||
cl.Pop()
|
||||
}
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
|
||||
@@ -35,26 +35,24 @@ func TestTransformMacro(t *testing.T) {
|
||||
m2 := op.Record(o)
|
||||
paint.ColorOp{Color: red}.Add(o)
|
||||
// Simulate a draw text call
|
||||
stack := op.Save(o)
|
||||
op.Offset(f32.Pt(0, 10)).Add(o)
|
||||
t := op.Offset(f32.Pt(0, 10)).Push(o)
|
||||
|
||||
// Apply the clip-path.
|
||||
c.Add(o)
|
||||
cl := c.Push(o)
|
||||
|
||||
paint.PaintOp{}.Add(o)
|
||||
stack.Load()
|
||||
cl.Pop()
|
||||
t.Pop()
|
||||
|
||||
c2 := m2.Stop()
|
||||
|
||||
// Call each of them in a transform
|
||||
s1 := op.Save(o)
|
||||
op.Offset(f32.Pt(0, 0)).Add(o)
|
||||
t = op.Offset(f32.Pt(0, 0)).Push(o)
|
||||
c1.Add(o)
|
||||
s1.Load()
|
||||
s2 := op.Save(o)
|
||||
op.Offset(f32.Pt(0, 0)).Add(o)
|
||||
t.Pop()
|
||||
t = op.Offset(f32.Pt(0, 0)).Push(o)
|
||||
c2.Add(o)
|
||||
s2.Load()
|
||||
t.Pop()
|
||||
}, func(r result) {
|
||||
r.expect(5, 15, colornames.Red)
|
||||
r.expect(15, 15, colornames.Black)
|
||||
@@ -75,9 +73,9 @@ func TestRepeatedPaintsZ(t *testing.T) {
|
||||
builder.Line(f32.Pt(-10, 0))
|
||||
builder.Line(f32.Pt(0, -10))
|
||||
p := builder.End()
|
||||
clip.Outline{
|
||||
defer clip.Outline{
|
||||
Path: p,
|
||||
}.Op().Add(o)
|
||||
}.Op().Push(o).Pop()
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
r.expect(5, 5, colornames.Red)
|
||||
@@ -91,10 +89,10 @@ func TestNoClipFromPaint(t *testing.T) {
|
||||
// by leaving any clip paths in place.
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(20, 20), math.Pi/4)
|
||||
op.Affine(a).Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
|
||||
a = f32.Affine2D{}.Rotate(f32.Pt(20, 20), -math.Pi/4)
|
||||
op.Affine(a).Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
|
||||
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
||||
}, func(r result) {
|
||||
@@ -107,31 +105,31 @@ func TestNoClipFromPaint(t *testing.T) {
|
||||
|
||||
func TestDeferredPaint(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
state := op.Save(o)
|
||||
clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o)
|
||||
cl := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o)
|
||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, G: 0xff}}.Add(o)
|
||||
paint.PaintOp{}.Add(o)
|
||||
cl.Pop()
|
||||
|
||||
op.Affine(f32.Affine2D{}.Offset(f32.Pt(20, 20))).Add(o)
|
||||
t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(20, 20))).Push(o)
|
||||
m := op.Record(o)
|
||||
clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o)
|
||||
cl2 := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o)
|
||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, R: 0xff, G: 0xff}}.Add(o)
|
||||
paint.PaintOp{}.Add(o)
|
||||
cl2.Pop()
|
||||
paintMacro := m.Stop()
|
||||
op.Defer(o, paintMacro)
|
||||
t.Pop()
|
||||
|
||||
state.Load()
|
||||
op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Add(o)
|
||||
clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o)
|
||||
defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o).Pop()
|
||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o)
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
}
|
||||
|
||||
func constSqPath() op.CallOp {
|
||||
func constSqPath() clip.Op {
|
||||
innerOps := new(op.Ops)
|
||||
m := op.Record(innerOps)
|
||||
builder := clip.Path{}
|
||||
builder.Begin(innerOps)
|
||||
builder.Move(f32.Pt(0, 0))
|
||||
@@ -140,22 +138,20 @@ func constSqPath() op.CallOp {
|
||||
builder.Line(f32.Pt(-10, 0))
|
||||
builder.Line(f32.Pt(0, -10))
|
||||
p := builder.End()
|
||||
clip.Outline{Path: p}.Op().Add(innerOps)
|
||||
return m.Stop()
|
||||
return clip.Outline{Path: p}.Op()
|
||||
}
|
||||
|
||||
func constSqCirc() op.CallOp {
|
||||
func constSqCirc() clip.Op {
|
||||
innerOps := new(op.Ops)
|
||||
m := op.Record(innerOps)
|
||||
clip.RRect{Rect: f32.Rect(0, 0, 40, 40),
|
||||
NW: 20, NE: 20, SW: 20, SE: 20}.Add(innerOps)
|
||||
return m.Stop()
|
||||
return clip.RRect{Rect: f32.Rect(0, 0, 40, 40),
|
||||
NW: 20, NE: 20, SW: 20, SE: 20}.Op(innerOps)
|
||||
}
|
||||
|
||||
func drawChild(ops *op.Ops, text op.CallOp) op.CallOp {
|
||||
func drawChild(ops *op.Ops, text clip.Op) op.CallOp {
|
||||
r1 := op.Record(ops)
|
||||
text.Add(ops)
|
||||
cl := text.Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
cl.Pop()
|
||||
return r1.Stop()
|
||||
}
|
||||
|
||||
@@ -166,14 +162,10 @@ func TestReuseStencil(t *testing.T) {
|
||||
c2 := drawChild(ops, txt)
|
||||
|
||||
// lay out the children
|
||||
stack1 := op.Save(ops)
|
||||
c1.Add(ops)
|
||||
stack1.Load()
|
||||
|
||||
stack2 := op.Save(ops)
|
||||
op.Offset(f32.Pt(0, 50)).Add(ops)
|
||||
defer op.Offset(f32.Pt(0, 50)).Push(ops).Pop()
|
||||
c2.Add(ops)
|
||||
stack2.Load()
|
||||
}, func(r result) {
|
||||
r.expect(5, 5, colornames.Black)
|
||||
r.expect(5, 55, colornames.Black)
|
||||
@@ -187,11 +179,9 @@ func TestBuildOffscreen(t *testing.T) {
|
||||
|
||||
txt := constSqCirc()
|
||||
draw := func(off float32, o *op.Ops) {
|
||||
s := op.Save(o)
|
||||
op.Offset(f32.Pt(0, off)).Add(o)
|
||||
txt.Add(o)
|
||||
defer op.Offset(f32.Pt(0, off)).Push(o).Pop()
|
||||
defer txt.Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
s.Load()
|
||||
}
|
||||
|
||||
multiRun(t,
|
||||
@@ -214,8 +204,8 @@ func TestBuildOffscreen(t *testing.T) {
|
||||
|
||||
func TestNegativeOverlaps(t *testing.T) {
|
||||
run(t, func(ops *op.Ops) {
|
||||
clip.RRect{Rect: f32.Rect(50, 50, 100, 100)}.Add(ops)
|
||||
clip.Rect(image.Rect(0, 120, 100, 122)).Add(ops)
|
||||
defer clip.RRect{Rect: f32.Rect(50, 50, 100, 100)}.Push(ops).Pop()
|
||||
clip.Rect(image.Rect(0, 120, 100, 122)).Push(ops).Pop()
|
||||
paint.PaintOp{}.Add(ops)
|
||||
}, func(r result) {
|
||||
r.expect(60, 60, transparent)
|
||||
@@ -227,13 +217,8 @@ func TestNegativeOverlaps(t *testing.T) {
|
||||
|
||||
func TestDepthOverlap(t *testing.T) {
|
||||
run(t, func(ops *op.Ops) {
|
||||
stack := op.Save(ops)
|
||||
paint.FillShape(ops, red, clip.Rect{Max: image.Pt(128, 64)}.Op())
|
||||
stack.Load()
|
||||
|
||||
stack = op.Save(ops)
|
||||
paint.FillShape(ops, green, clip.Rect{Max: image.Pt(64, 128)}.Op())
|
||||
stack.Load()
|
||||
}, func(r result) {
|
||||
r.expect(96, 32, colornames.Red)
|
||||
r.expect(32, 96, colornames.Green)
|
||||
@@ -272,12 +257,13 @@ func TestLinearGradient(t *testing.T) {
|
||||
Stop2: f32.Pt(gr.Max.X, gr.Min.Y),
|
||||
Color2: g.To,
|
||||
}.Add(ops)
|
||||
st := op.Save(ops)
|
||||
clip.RRect{Rect: gr}.Add(ops)
|
||||
op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Add(ops)
|
||||
scale(pixelAligned.Dx()/128, 1).Add(ops)
|
||||
cl := clip.RRect{Rect: gr}.Push(ops)
|
||||
t1 := op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Push(ops)
|
||||
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
st.Load()
|
||||
t2.Pop()
|
||||
t1.Pop()
|
||||
cl.Pop()
|
||||
gr = gr.Add(f32.Pt(0, gradienth))
|
||||
}
|
||||
}, func(r result) {
|
||||
@@ -302,10 +288,9 @@ func TestLinearGradientAngled(t *testing.T) {
|
||||
Stop2: f32.Pt(0, 0),
|
||||
Color2: red,
|
||||
}.Add(ops)
|
||||
st := op.Save(ops)
|
||||
clip.Rect(image.Rect(0, 0, 64, 64)).Add(ops)
|
||||
cl := clip.Rect(image.Rect(0, 0, 64, 64)).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
st.Load()
|
||||
cl.Pop()
|
||||
|
||||
paint.LinearGradientOp{
|
||||
Stop1: f32.Pt(64, 64),
|
||||
@@ -313,10 +298,9 @@ func TestLinearGradientAngled(t *testing.T) {
|
||||
Stop2: f32.Pt(128, 0),
|
||||
Color2: green,
|
||||
}.Add(ops)
|
||||
st = op.Save(ops)
|
||||
clip.Rect(image.Rect(64, 0, 128, 64)).Add(ops)
|
||||
cl = clip.Rect(image.Rect(64, 0, 128, 64)).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
st.Load()
|
||||
cl.Pop()
|
||||
|
||||
paint.LinearGradientOp{
|
||||
Stop1: f32.Pt(64, 64),
|
||||
@@ -324,10 +308,9 @@ func TestLinearGradientAngled(t *testing.T) {
|
||||
Stop2: f32.Pt(128, 128),
|
||||
Color2: blue,
|
||||
}.Add(ops)
|
||||
st = op.Save(ops)
|
||||
clip.Rect(image.Rect(64, 64, 128, 128)).Add(ops)
|
||||
cl = clip.Rect(image.Rect(64, 64, 128, 128)).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
st.Load()
|
||||
cl.Pop()
|
||||
|
||||
paint.LinearGradientOp{
|
||||
Stop1: f32.Pt(64, 64),
|
||||
@@ -335,10 +318,9 @@ func TestLinearGradientAngled(t *testing.T) {
|
||||
Stop2: f32.Pt(0, 128),
|
||||
Color2: magenta,
|
||||
}.Add(ops)
|
||||
st = op.Save(ops)
|
||||
clip.Rect(image.Rect(0, 64, 64, 128)).Add(ops)
|
||||
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
st.Load()
|
||||
cl.Pop()
|
||||
}, func(r result) {})
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
func TestPaintOffset(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
op.Offset(f32.Pt(10, 20)).Add(o)
|
||||
defer op.Offset(f32.Pt(10, 20)).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
@@ -30,7 +30,7 @@ func TestPaintOffset(t *testing.T) {
|
||||
func TestPaintRotate(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/8)
|
||||
op.Affine(a).Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(40, 40, colornames.Red)
|
||||
@@ -43,7 +43,7 @@ func TestPaintRotate(t *testing.T) {
|
||||
func TestPaintShear(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
|
||||
op.Affine(a).Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(10, 30, transparent)
|
||||
@@ -52,8 +52,8 @@ func TestPaintShear(t *testing.T) {
|
||||
|
||||
func TestClipPaintOffset(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
clip.RRect{Rect: f32.Rect(10, 10, 30, 30)}.Add(o)
|
||||
op.Offset(f32.Pt(20, 20)).Add(o)
|
||||
defer clip.RRect{Rect: f32.Rect(10, 10, 30, 30)}.Push(o).Pop()
|
||||
defer op.Offset(f32.Pt(20, 20)).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 100, 100)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
@@ -65,8 +65,8 @@ func TestClipPaintOffset(t *testing.T) {
|
||||
|
||||
func TestClipOffset(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
op.Offset(f32.Pt(20, 20)).Add(o)
|
||||
clip.RRect{Rect: f32.Rect(10, 10, 30, 30)}.Add(o)
|
||||
defer op.Offset(f32.Pt(20, 20)).Push(o).Pop()
|
||||
defer clip.RRect{Rect: f32.Rect(10, 10, 30, 30)}.Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 100, 100)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
@@ -80,8 +80,8 @@ func TestClipOffset(t *testing.T) {
|
||||
func TestClipScale(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
|
||||
op.Affine(a).Add(o)
|
||||
clip.RRect{Rect: f32.Rect(10, 10, 20, 20)}.Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer clip.RRect{Rect: f32.Rect(10, 10, 20, 20)}.Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(19+10, 19+10, transparent)
|
||||
@@ -93,8 +93,8 @@ func TestClipScale(t *testing.T) {
|
||||
|
||||
func TestClipRotate(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
op.Affine(f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/4)).Add(o)
|
||||
clip.RRect{Rect: f32.Rect(30, 30, 50, 50)}.Add(o)
|
||||
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/4)).Push(o).Pop()
|
||||
defer clip.RRect{Rect: f32.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(39, 39, transparent)
|
||||
@@ -105,9 +105,9 @@ func TestClipRotate(t *testing.T) {
|
||||
|
||||
func TestOffsetTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
op.Offset(f32.Pt(15, 15)).Add(o)
|
||||
defer op.Offset(f32.Pt(15, 15)).Push(o).Pop()
|
||||
squares.Add(o)
|
||||
scale(50.0/512, 50.0/512).Add(o)
|
||||
defer scale(50.0/512, 50.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
r.expect(14, 20, transparent)
|
||||
@@ -119,10 +119,10 @@ func TestOffsetTexture(t *testing.T) {
|
||||
|
||||
func TestOffsetScaleTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
op.Offset(f32.Pt(15, 15)).Add(o)
|
||||
defer op.Offset(f32.Pt(15, 15)).Push(o).Pop()
|
||||
squares.Add(o)
|
||||
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 1))).Add(o)
|
||||
scale(50.0/512, 50.0/512).Add(o)
|
||||
defer op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop()
|
||||
defer scale(50.0/512, 50.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
r.expect(114, 64, colornames.Blue)
|
||||
@@ -132,11 +132,10 @@ func TestOffsetScaleTexture(t *testing.T) {
|
||||
|
||||
func TestRotateTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
defer op.Save(o).Load()
|
||||
squares.Add(o)
|
||||
a := f32.Affine2D{}.Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
|
||||
op.Affine(a).Add(o)
|
||||
scale(20.0/512, 20.0/512).Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer scale(20.0/512, 20.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
r.expect(40, 40-12, colornames.Blue)
|
||||
@@ -148,10 +147,10 @@ func TestRotateClipTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
squares.Add(o)
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), math.Pi/8)
|
||||
op.Affine(a).Add(o)
|
||||
clip.RRect{Rect: f32.Rect(30, 30, 50, 50)}.Add(o)
|
||||
op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Add(o)
|
||||
scale(60.0/512, 60.0/512).Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer clip.RRect{Rect: f32.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||
defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer scale(60.0/512, 60.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
@@ -167,13 +166,13 @@ func TestComplicatedTransform(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
squares.Add(o)
|
||||
|
||||
clip.RRect{Rect: f32.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Add(o)
|
||||
defer clip.RRect{Rect: f32.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop()
|
||||
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
|
||||
op.Affine(a).Add(o)
|
||||
clip.RRect{Rect: f32.Rect(0, 0, 50, 40)}.Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer clip.RRect{Rect: f32.Rect(0, 0, 50, 40)}.Push(o).Pop()
|
||||
|
||||
scale(50.0/512, 50.0/512).Add(o)
|
||||
defer scale(50.0/512, 50.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
r.expect(20, 5, transparent)
|
||||
@@ -184,13 +183,13 @@ func TestTransformOrder(t *testing.T) {
|
||||
// check the ordering of operations bot in affine and in gpu stack.
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Offset(f32.Pt(64, 64))
|
||||
op.Affine(a).Add(o)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
|
||||
b := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(8, 8))
|
||||
op.Affine(b).Add(o)
|
||||
defer op.Affine(b).Push(o).Pop()
|
||||
|
||||
c := f32.Affine2D{}.Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
|
||||
op.Affine(c).Add(o)
|
||||
defer op.Affine(c).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
|
||||
}, func(r result) {
|
||||
// centered and with radius 40
|
||||
|
||||
+18
-17
@@ -12,12 +12,14 @@ const (
|
||||
TypeCall
|
||||
TypeDefer
|
||||
TypeTransform
|
||||
TypePopTransform
|
||||
TypeInvalidate
|
||||
TypeImage
|
||||
TypePaint
|
||||
TypeColor
|
||||
TypeLinearGradient
|
||||
TypeArea
|
||||
TypePopArea
|
||||
TypePointerInput
|
||||
TypeClipboardRead
|
||||
TypeClipboardWrite
|
||||
@@ -28,6 +30,7 @@ const (
|
||||
TypeLoad
|
||||
TypeAux
|
||||
TypeClip
|
||||
TypePopClip
|
||||
TypeProfile
|
||||
TypeCursor
|
||||
TypePath
|
||||
@@ -38,13 +41,15 @@ const (
|
||||
TypeMacroLen = 1 + 4 + 4
|
||||
TypeCallLen = 1 + 4 + 4
|
||||
TypeDeferLen = 1
|
||||
TypeTransformLen = 1 + 4*6
|
||||
TypeTransformLen = 1 + 1 + 4*6
|
||||
TypePopTransformLen = 1
|
||||
TypeRedrawLen = 1 + 8
|
||||
TypeImageLen = 1
|
||||
TypePaintLen = 1
|
||||
TypeColorLen = 1 + 4
|
||||
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
||||
TypeAreaLen = 1 + 1 + 1 + 4*4
|
||||
TypePopAreaLen = 1
|
||||
TypePointerInputLen = 1 + 1 + 1 + 2*4 + 2*4
|
||||
TypeClipboardReadLen = 1
|
||||
TypeClipboardWriteLen = 1
|
||||
@@ -52,41 +57,30 @@ const (
|
||||
TypeKeyFocusLen = 1 + 1
|
||||
TypeKeySoftKeyboardLen = 1 + 1
|
||||
TypeSaveLen = 1 + 4
|
||||
TypeLoadLen = 1 + 1 + 4
|
||||
TypeLoadLen = 1 + 4
|
||||
TypeAuxLen = 1
|
||||
TypeClipLen = 1 + 4*4 + 1
|
||||
TypeClipLen = 1 + 4*4 + 1 + 1
|
||||
TypePopClipLen = 1
|
||||
TypeProfileLen = 1
|
||||
TypeCursorLen = 1 + 1
|
||||
TypePathLen = 8 + 1
|
||||
TypeStrokeLen = 1 + 4
|
||||
)
|
||||
|
||||
// StateMask is a bitmask of state types a load operation
|
||||
// should restore.
|
||||
type StateMask uint8
|
||||
|
||||
const (
|
||||
TransformState StateMask = 1 << iota
|
||||
|
||||
AllState = ^StateMask(0)
|
||||
)
|
||||
|
||||
// InitialStateID is the ID for saving and loading
|
||||
// the initial operation state.
|
||||
const InitialStateID = 0
|
||||
|
||||
func (t OpType) Size() int {
|
||||
return [...]int{
|
||||
TypeMacroLen,
|
||||
TypeCallLen,
|
||||
TypeDeferLen,
|
||||
TypeTransformLen,
|
||||
TypePopTransformLen,
|
||||
TypeRedrawLen,
|
||||
TypeImageLen,
|
||||
TypePaintLen,
|
||||
TypeColorLen,
|
||||
TypeLinearGradientLen,
|
||||
TypeAreaLen,
|
||||
TypePopAreaLen,
|
||||
TypePointerInputLen,
|
||||
TypeClipboardReadLen,
|
||||
TypeClipboardWriteLen,
|
||||
@@ -97,6 +91,7 @@ func (t OpType) Size() int {
|
||||
TypeLoadLen,
|
||||
TypeAuxLen,
|
||||
TypeClipLen,
|
||||
TypePopClipLen,
|
||||
TypeProfileLen,
|
||||
TypeCursorLen,
|
||||
TypePathLen,
|
||||
@@ -125,6 +120,8 @@ func (t OpType) String() string {
|
||||
return "Defer"
|
||||
case TypeTransform:
|
||||
return "Transform"
|
||||
case TypePopTransform:
|
||||
return "PopTransform"
|
||||
case TypeInvalidate:
|
||||
return "Invalidate"
|
||||
case TypeImage:
|
||||
@@ -137,6 +134,8 @@ func (t OpType) String() string {
|
||||
return "LinearGradient"
|
||||
case TypeArea:
|
||||
return "Area"
|
||||
case TypePopArea:
|
||||
return "PopArea"
|
||||
case TypePointerInput:
|
||||
return "PointerInput"
|
||||
case TypeClipboardRead:
|
||||
@@ -157,6 +156,8 @@ func (t OpType) String() string {
|
||||
return "Aux"
|
||||
case TypeClip:
|
||||
return "Clip"
|
||||
case TypePopClip:
|
||||
return "PopClip"
|
||||
case TypeProfile:
|
||||
return "Profile"
|
||||
case TypeCursor:
|
||||
|
||||
+7
-6
@@ -22,11 +22,12 @@ func EncodeCommand(out []byte, cmd scene.Command) {
|
||||
copy(out, byteslice.Uint32(cmd[:]))
|
||||
}
|
||||
|
||||
func DecodeTransform(data []byte) (t f32.Affine2D) {
|
||||
func DecodeTransform(data []byte) (t f32.Affine2D, push bool) {
|
||||
if opconst.OpType(data[0]) != opconst.TypeTransform {
|
||||
panic("invalid op")
|
||||
}
|
||||
data = data[1:]
|
||||
push = data[1] != 0
|
||||
data = data[2:]
|
||||
data = data[:4*6]
|
||||
|
||||
bo := binary.LittleEndian
|
||||
@@ -36,7 +37,7 @@ func DecodeTransform(data []byte) (t f32.Affine2D) {
|
||||
d := math.Float32frombits(bo.Uint32(data[4*3:]))
|
||||
e := math.Float32frombits(bo.Uint32(data[4*4:]))
|
||||
f := math.Float32frombits(bo.Uint32(data[4*5:]))
|
||||
return f32.NewAffine2D(a, b, c, d, e, f)
|
||||
return f32.NewAffine2D(a, b, c, d, e, f), push
|
||||
}
|
||||
|
||||
// DecodeSave decodes the state id of a save op.
|
||||
@@ -48,11 +49,11 @@ func DecodeSave(data []byte) int {
|
||||
return int(bo.Uint32(data[1:]))
|
||||
}
|
||||
|
||||
// DecodeLoad decodes the state id and mask of a load op.
|
||||
func DecodeLoad(data []byte) (int, opconst.StateMask) {
|
||||
// DecodeLoad decodes the state id of a load op.
|
||||
func DecodeLoad(data []byte) int {
|
||||
if opconst.OpType(data[0]) != opconst.TypeLoad {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
return int(bo.Uint32(data[2:])), opconst.StateMask(data[1])
|
||||
return int(bo.Uint32(data[1:]))
|
||||
}
|
||||
|
||||
+7
-9
@@ -41,25 +41,23 @@ operations is the intersection of the areas.
|
||||
|
||||
Matching events
|
||||
|
||||
StackOp operations and input handlers form an implicit tree.
|
||||
Each stack operation is a node, and each input handler is associated
|
||||
with the most recent node.
|
||||
Areas form an implicit tree, with input handlers as leaves. The children of
|
||||
an area is every area and handler added between its Push and corresponding Pop.
|
||||
|
||||
For example:
|
||||
|
||||
ops := new(op.Ops)
|
||||
var stack op.StackOp
|
||||
var h1, h2 *Handler
|
||||
|
||||
state := op.Save(ops)
|
||||
area := pointer.Rect(...).Push(ops)
|
||||
pointer.InputOp{Tag: h1}.Add(Ops)
|
||||
state.Load()
|
||||
area.Pop()
|
||||
|
||||
state = op.Save(ops)
|
||||
area := pointer.Rect(...).Push(ops)
|
||||
pointer.InputOp{Tag: h2}.Add(ops)
|
||||
state.Load()
|
||||
area.Pop()
|
||||
|
||||
implies a tree of two inner nodes, each with one pointer handler.
|
||||
implies a tree of two inner nodes, each with one pointer handler attached.
|
||||
|
||||
When determining which handlers match an Event, only handlers whose
|
||||
areas contain the event position are considered. The matching
|
||||
|
||||
+29
-8
@@ -54,6 +54,13 @@ type AreaOp struct {
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
// AreaStack represents an AreaOp on the stack of areas.
|
||||
type AreaStack struct {
|
||||
ops *op.Ops
|
||||
id op.StackID
|
||||
macroID int
|
||||
}
|
||||
|
||||
// CursorNameOp sets the cursor for the current area.
|
||||
type CursorNameOp struct {
|
||||
Name CursorName
|
||||
@@ -185,18 +192,32 @@ func Ellipse(size image.Rectangle) AreaOp {
|
||||
}
|
||||
}
|
||||
|
||||
func (op AreaOp) Add(o *op.Ops) {
|
||||
data := o.Write(opconst.TypeAreaLen)
|
||||
// Push the current area to the stack and intersects the current area with the
|
||||
// area represented by o.
|
||||
func (o AreaOp) Push(ops *op.Ops) AreaStack {
|
||||
id, macroID := ops.PushOp(op.AreaStack)
|
||||
o.add(ops, true)
|
||||
return AreaStack{ops: ops, id: id, macroID: macroID}
|
||||
}
|
||||
|
||||
func (o AreaOp) add(ops *op.Ops, push bool) {
|
||||
data := ops.Write(opconst.TypeAreaLen)
|
||||
data[0] = byte(opconst.TypeArea)
|
||||
data[1] = byte(op.kind)
|
||||
if op.PassThrough {
|
||||
data[1] = byte(o.kind)
|
||||
if o.PassThrough {
|
||||
data[2] = 1
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
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))
|
||||
bo.PutUint32(data[3:], uint32(o.rect.Min.X))
|
||||
bo.PutUint32(data[7:], uint32(o.rect.Min.Y))
|
||||
bo.PutUint32(data[11:], uint32(o.rect.Max.X))
|
||||
bo.PutUint32(data[15:], uint32(o.rect.Max.Y))
|
||||
}
|
||||
|
||||
func (o AreaStack) Pop() {
|
||||
o.ops.PopOp(op.AreaStack, o.id, o.macroID)
|
||||
data := o.ops.Write(opconst.TypePopAreaLen)
|
||||
data[0] = byte(opconst.TypePopArea)
|
||||
}
|
||||
|
||||
func (op CursorNameOp) Add(o *op.Ops) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -60,22 +60,14 @@ func TestKeyStacked(t *testing.T) {
|
||||
ops := new(op.Ops)
|
||||
r := new(Router)
|
||||
|
||||
s := op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.FocusOp{Tag: nil}.Add(ops)
|
||||
s.Load()
|
||||
s = op.Save(ops)
|
||||
key.SoftKeyboardOp{Show: false}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
key.FocusOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Load()
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[2]}.Add(ops)
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
s.Load()
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[3]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -107,16 +99,12 @@ func TestKeyRemoveFocus(t *testing.T) {
|
||||
r := new(Router)
|
||||
|
||||
// New InputOp with Focus and Keyboard:
|
||||
s := op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.FocusOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
// New InputOp without any focus:
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -132,19 +120,13 @@ func TestKeyRemoveFocus(t *testing.T) {
|
||||
ops.Reset()
|
||||
|
||||
// Will get the focus removed:
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
// Unchanged:
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
// Remove focus by focusing on a tag that don't exist.
|
||||
s = op.Save(ops)
|
||||
key.FocusOp{Tag: new(int)}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -154,13 +136,9 @@ func TestKeyRemoveFocus(t *testing.T) {
|
||||
|
||||
ops.Reset()
|
||||
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -173,17 +151,13 @@ func TestKeyRemoveFocus(t *testing.T) {
|
||||
|
||||
// Set focus to InputOp which already
|
||||
// exists in the previous frame:
|
||||
s = op.Save(ops)
|
||||
key.FocusOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
// Remove focus.
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
key.FocusOp{Tag: nil}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -198,16 +172,12 @@ func TestKeyFocusedInvisible(t *testing.T) {
|
||||
r := new(Router)
|
||||
|
||||
// Set new InputOp with focus:
|
||||
s := op.Save(ops)
|
||||
key.FocusOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
// Set new InputOp without focus:
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -223,9 +193,7 @@ func TestKeyFocusedInvisible(t *testing.T) {
|
||||
//
|
||||
|
||||
// Unchanged:
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
@@ -238,14 +206,10 @@ func TestKeyFocusedInvisible(t *testing.T) {
|
||||
|
||||
// Respawn the first element:
|
||||
// It must receive one `Event{Focus: false}`.
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
// Unchanged
|
||||
s = op.Save(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Load()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
|
||||
+31
-16
@@ -23,8 +23,10 @@ type pointerQueue struct {
|
||||
pointers []pointerInfo
|
||||
reader ops.Reader
|
||||
|
||||
nodeStack []int
|
||||
transStack []f32.Affine2D
|
||||
// states holds the storage for save/restore ops.
|
||||
states []collectState
|
||||
states []f32.Affine2D
|
||||
scratch []event.Tag
|
||||
}
|
||||
|
||||
@@ -81,6 +83,7 @@ type areaKind uint8
|
||||
type collectState struct {
|
||||
t f32.Affine2D
|
||||
node int
|
||||
pass bool
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -88,32 +91,30 @@ const (
|
||||
areaEllipse
|
||||
)
|
||||
|
||||
func (q *pointerQueue) save(id int, state collectState) {
|
||||
func (q *pointerQueue) save(id int, state f32.Affine2D) {
|
||||
if extra := id - len(q.states) + 1; extra > 0 {
|
||||
q.states = append(q.states, make([]collectState, extra)...)
|
||||
q.states = append(q.states, make([]f32.Affine2D, extra)...)
|
||||
}
|
||||
q.states[id] = state
|
||||
}
|
||||
|
||||
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
|
||||
state := collectState{
|
||||
node: -1,
|
||||
var state collectState
|
||||
reset := func() {
|
||||
state = collectState{
|
||||
node: -1,
|
||||
}
|
||||
}
|
||||
q.save(opconst.InitialStateID, state)
|
||||
reset()
|
||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypeSave:
|
||||
id := ops.DecodeSave(encOp.Data)
|
||||
q.save(id, state)
|
||||
q.save(id, state.t)
|
||||
case opconst.TypeLoad:
|
||||
id, mask := ops.DecodeLoad(encOp.Data)
|
||||
s := q.states[id]
|
||||
if mask&opconst.TransformState != 0 {
|
||||
state.t = s.t
|
||||
}
|
||||
if mask&^opconst.TransformState != 0 {
|
||||
state = s
|
||||
}
|
||||
reset()
|
||||
id := ops.DecodeLoad(encOp.Data)
|
||||
state.t = q.states[id]
|
||||
case opconst.TypeArea:
|
||||
var op areaOp
|
||||
op.Decode(encOp.Data)
|
||||
@@ -123,14 +124,26 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
|
||||
area = n.area
|
||||
}
|
||||
q.areas = append(q.areas, areaNode{trans: state.t, next: area, area: op, pass: op.pass})
|
||||
q.nodeStack = append(q.nodeStack, state.node)
|
||||
q.hitTree = append(q.hitTree, hitNode{
|
||||
next: state.node,
|
||||
area: len(q.areas) - 1,
|
||||
})
|
||||
state.node = len(q.hitTree) - 1
|
||||
case opconst.TypePopArea:
|
||||
n := len(q.nodeStack)
|
||||
state.node = q.nodeStack[n-1]
|
||||
q.nodeStack = q.nodeStack[:n-1]
|
||||
case opconst.TypeTransform:
|
||||
dop := ops.DecodeTransform(encOp.Data)
|
||||
dop, push := ops.DecodeTransform(encOp.Data)
|
||||
if push {
|
||||
q.transStack = append(q.transStack, state.t)
|
||||
}
|
||||
state.t = state.t.Mul(dop)
|
||||
case opconst.TypePopTransform:
|
||||
n := len(q.transStack)
|
||||
state.t = q.transStack[n-1]
|
||||
q.transStack = q.transStack[:n-1]
|
||||
case opconst.TypePointerInput:
|
||||
op := pointer.InputOp{
|
||||
Tag: encOp.Refs[0].(event.Tag),
|
||||
@@ -242,6 +255,8 @@ func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
|
||||
}
|
||||
q.hitTree = q.hitTree[:0]
|
||||
q.areas = q.areas[:0]
|
||||
q.nodeStack = q.nodeStack[:0]
|
||||
q.transStack = q.transStack[:0]
|
||||
q.cursors = q.cursors[:0]
|
||||
q.reader.Reset(root)
|
||||
q.collectHandlers(&q.reader, events)
|
||||
|
||||
+23
-19
@@ -122,11 +122,13 @@ func TestPointerMove(t *testing.T) {
|
||||
types := pointer.Move | pointer.Enter | pointer.Leave
|
||||
|
||||
// Handler 1 area: (0, 0) - (100, 100)
|
||||
pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
|
||||
r1 := pointer.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
||||
pointer.InputOp{Tag: handler1, Types: types}.Add(&ops)
|
||||
// Handler 2 area: (50, 50) - (100, 100) (areas intersect).
|
||||
pointer.Rect(image.Rect(50, 50, 200, 200)).Add(&ops)
|
||||
r2 := pointer.Rect(image.Rect(50, 50, 200, 200)).Push(&ops)
|
||||
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
|
||||
r2.Pop()
|
||||
r1.Pop()
|
||||
|
||||
var r Router
|
||||
r.Frame(&ops)
|
||||
@@ -157,11 +159,12 @@ func TestPointerMove(t *testing.T) {
|
||||
func TestPointerTypes(t *testing.T) {
|
||||
handler := new(int)
|
||||
var ops op.Ops
|
||||
pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
|
||||
r1 := pointer.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
||||
pointer.InputOp{
|
||||
Tag: handler,
|
||||
Types: pointer.Press | pointer.Release,
|
||||
}.Add(&ops)
|
||||
r1.Pop()
|
||||
|
||||
var r Router
|
||||
r.Frame(&ops)
|
||||
@@ -188,28 +191,29 @@ func TestPointerPriority(t *testing.T) {
|
||||
handler3 := new(int)
|
||||
var ops op.Ops
|
||||
|
||||
st := op.Save(&ops)
|
||||
pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
|
||||
r1 := pointer.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
||||
pointer.InputOp{
|
||||
Tag: handler1,
|
||||
Types: pointer.Scroll,
|
||||
ScrollBounds: image.Rectangle{Max: image.Point{X: 100}},
|
||||
}.Add(&ops)
|
||||
|
||||
pointer.Rect(image.Rect(0, 0, 100, 50)).Add(&ops)
|
||||
r2 := pointer.Rect(image.Rect(0, 0, 100, 50)).Push(&ops)
|
||||
pointer.InputOp{
|
||||
Tag: handler2,
|
||||
Types: pointer.Scroll,
|
||||
ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
|
||||
}.Add(&ops)
|
||||
st.Load()
|
||||
r2.Pop()
|
||||
r1.Pop()
|
||||
|
||||
pointer.Rect(image.Rect(0, 100, 100, 200)).Add(&ops)
|
||||
r3 := pointer.Rect(image.Rect(0, 100, 100, 200)).Push(&ops)
|
||||
pointer.InputOp{
|
||||
Tag: handler3,
|
||||
Types: pointer.Scroll,
|
||||
ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
|
||||
}.Add(&ops)
|
||||
r3.Pop()
|
||||
|
||||
var r Router
|
||||
r.Frame(&ops)
|
||||
@@ -357,12 +361,11 @@ func TestMultipleAreas(t *testing.T) {
|
||||
var ops op.Ops
|
||||
|
||||
addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100))
|
||||
st := op.Save(&ops)
|
||||
pointer.Rect(image.Rect(50, 50, 200, 200)).Add(&ops)
|
||||
r1 := pointer.Rect(image.Rect(50, 50, 200, 200)).Push(&ops)
|
||||
// Second area has no Types set, yet should receive events because
|
||||
// Types for the same handles are or-ed together.
|
||||
pointer.InputOp{Tag: handler}.Add(&ops)
|
||||
st.Load()
|
||||
r1.Pop()
|
||||
|
||||
var r Router
|
||||
r.Frame(&ops)
|
||||
@@ -392,12 +395,14 @@ func TestPointerEnterLeaveNested(t *testing.T) {
|
||||
types := pointer.Press | pointer.Move | pointer.Release | pointer.Enter | pointer.Leave
|
||||
|
||||
// Handler 1 area: (0, 0) - (100, 100)
|
||||
pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
|
||||
r1 := pointer.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
||||
pointer.InputOp{Tag: handler1, Types: types}.Add(&ops)
|
||||
|
||||
// Handler 2 area: (25, 25) - (75, 75) (nested within first).
|
||||
pointer.Rect(image.Rect(25, 25, 75, 75)).Add(&ops)
|
||||
r2 := pointer.Rect(image.Rect(25, 25, 75, 75)).Push(&ops)
|
||||
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
|
||||
r2.Pop()
|
||||
r1.Pop()
|
||||
|
||||
var r Router
|
||||
r.Frame(&ops)
|
||||
@@ -538,7 +543,7 @@ func TestCursorNameOp(t *testing.T) {
|
||||
var widget2 func()
|
||||
widget := func() {
|
||||
// This is the area where the cursor is changed to CursorPointer.
|
||||
pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Add(ops)
|
||||
defer pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Push(ops).Pop()
|
||||
// The cursor is checked and changed upon cursor movement.
|
||||
pointer.InputOp{Tag: &h}.Add(ops)
|
||||
pointer.CursorNameOp{Name: pointer.CursorPointer}.Add(ops)
|
||||
@@ -652,8 +657,7 @@ func TestCursorNameOp(t *testing.T) {
|
||||
// addPointerHandler adds a pointer.InputOp for the tag in a
|
||||
// rectangular area.
|
||||
func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {
|
||||
defer op.Save(ops).Load()
|
||||
pointer.Rect(area).Add(ops)
|
||||
defer pointer.Rect(area).Push(ops).Pop()
|
||||
pointer.InputOp{
|
||||
Tag: tag,
|
||||
Types: pointer.Press | pointer.Release | pointer.Move | pointer.Drag | pointer.Enter | pointer.Leave,
|
||||
@@ -729,7 +733,7 @@ func BenchmarkRouterAdd(b *testing.B) {
|
||||
X: 100,
|
||||
Y: 100,
|
||||
},
|
||||
}).Add(&ops)
|
||||
}).Push(&ops)
|
||||
pointer.InputOp{
|
||||
Tag: handlers[i],
|
||||
Types: pointer.Move,
|
||||
@@ -755,7 +759,7 @@ var benchAreaOp areaOp
|
||||
|
||||
func BenchmarkAreaOp_Decode(b *testing.B) {
|
||||
ops := new(op.Ops)
|
||||
pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Add(ops)
|
||||
pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Push(ops).Pop()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchAreaOp.Decode(ops.Data())
|
||||
}
|
||||
@@ -763,7 +767,7 @@ func BenchmarkAreaOp_Decode(b *testing.B) {
|
||||
|
||||
func BenchmarkAreaOp_Hit(b *testing.B) {
|
||||
ops := new(op.Ops)
|
||||
pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Add(ops)
|
||||
pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Push(ops).Pop()
|
||||
benchAreaOp.Decode(ops.Data())
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchAreaOp.Hit(f32.Pt(50, 50))
|
||||
|
||||
+2
-3
@@ -183,11 +183,10 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
||||
cross = maxBaseline - b
|
||||
}
|
||||
}
|
||||
stack := op.Save(gtx.Ops)
|
||||
pt := f.Axis.Convert(image.Pt(mainSize, cross))
|
||||
op.Offset(FPt(pt)).Add(gtx.Ops)
|
||||
trans := op.Offset(FPt(pt)).Push(gtx.Ops)
|
||||
child.call.Add(gtx.Ops)
|
||||
stack.Load()
|
||||
trans.Pop()
|
||||
mainSize += f.Axis.Convert(dims.Size).X
|
||||
if i < len(children)-1 {
|
||||
switch f.Spacing {
|
||||
|
||||
+3
-5
@@ -141,11 +141,10 @@ func (in Inset) Layout(gtx Context, w Widget) Dimensions {
|
||||
if mcs.Min.Y > mcs.Max.Y {
|
||||
mcs.Min.Y = mcs.Max.Y
|
||||
}
|
||||
stack := op.Save(gtx.Ops)
|
||||
op.Offset(FPt(image.Point{X: left, Y: top})).Add(gtx.Ops)
|
||||
gtx.Constraints = mcs
|
||||
trans := op.Offset(FPt(image.Point{X: left, Y: top})).Push(gtx.Ops)
|
||||
dims := w(gtx)
|
||||
stack.Load()
|
||||
trans.Pop()
|
||||
return Dimensions{
|
||||
Size: dims.Size.Add(image.Point{X: right + left, Y: top + bottom}),
|
||||
Baseline: dims.Baseline + bottom,
|
||||
@@ -181,9 +180,8 @@ func (d Direction) Layout(gtx Context, w Widget) Dimensions {
|
||||
sz.Y = csn.Y
|
||||
}
|
||||
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
p := d.Position(dims.Size, sz)
|
||||
op.Offset(FPt(p)).Add(gtx.Ops)
|
||||
defer op.Offset(FPt(p)).Push(gtx.Ops).Pop()
|
||||
call.Add(gtx.Ops)
|
||||
|
||||
return Dimensions{
|
||||
|
||||
+5
-6
@@ -274,12 +274,12 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
||||
Min: l.Axis.Convert(image.Pt(min, -inf)),
|
||||
Max: l.Axis.Convert(image.Pt(max, inf)),
|
||||
}
|
||||
stack := op.Save(ops)
|
||||
clip.Rect(r).Add(ops)
|
||||
cl := clip.Rect(r).Push(ops)
|
||||
pt := l.Axis.Convert(image.Pt(pos, cross))
|
||||
op.Offset(FPt(pt)).Add(ops)
|
||||
trans := op.Offset(FPt(pt)).Push(ops)
|
||||
child.call.Add(ops)
|
||||
stack.Load()
|
||||
trans.Pop()
|
||||
cl.Pop()
|
||||
pos += childSize
|
||||
}
|
||||
atStart := l.Position.First == 0 && l.Position.Offset <= 0
|
||||
@@ -296,8 +296,7 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
||||
}
|
||||
dims := l.Axis.Convert(image.Pt(pos, maxCross))
|
||||
call := macro.Stop()
|
||||
defer op.Save(ops).Load()
|
||||
pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
|
||||
defer pointer.Rect(image.Rectangle{Max: dims}).Push(ops).Pop()
|
||||
|
||||
var min, max int
|
||||
if o := l.Position.Offset; o > 0 {
|
||||
|
||||
+2
-3
@@ -104,10 +104,9 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
||||
case SW, S, SE:
|
||||
p.Y = maxSZ.Y - sz.Y
|
||||
}
|
||||
stack := op.Save(gtx.Ops)
|
||||
op.Offset(FPt(p)).Add(gtx.Ops)
|
||||
trans := op.Offset(FPt(p)).Push(gtx.Ops)
|
||||
ch.call.Add(gtx.Ops)
|
||||
stack.Load()
|
||||
trans.Pop()
|
||||
if baseline == 0 {
|
||||
if b := ch.dims.Baseline; b != 0 {
|
||||
baseline = b + maxSZ.Y - sz.Y - p.Y
|
||||
|
||||
+37
-6
@@ -16,12 +16,6 @@ import (
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
var pathSeed maphash.Seed
|
||||
|
||||
func init() {
|
||||
pathSeed = maphash.MakeSeed()
|
||||
}
|
||||
|
||||
// Op represents a clip area. Op intersects the current clip area with
|
||||
// itself.
|
||||
type Op struct {
|
||||
@@ -32,7 +26,35 @@ type Op struct {
|
||||
dashes DashSpec
|
||||
}
|
||||
|
||||
// Stack represents an Op pushed on the clip stack.
|
||||
type Stack struct {
|
||||
ops *op.Ops
|
||||
id op.StackID
|
||||
macroID int
|
||||
}
|
||||
|
||||
var pathSeed maphash.Seed
|
||||
|
||||
func init() {
|
||||
pathSeed = maphash.MakeSeed()
|
||||
}
|
||||
|
||||
// Push saves the current clip state on the stack and updates the current
|
||||
// state to the intersection of the current p.
|
||||
func (p Op) Push(o *op.Ops) Stack {
|
||||
id, macroID := o.PushOp(op.ClipStack)
|
||||
p.add(o, true)
|
||||
return Stack{ops: o, id: id, macroID: macroID}
|
||||
}
|
||||
|
||||
// Add is like Push except it doesn't save the current state on the stack.
|
||||
//
|
||||
// Deprecated: use Push instead.
|
||||
func (p Op) Add(o *op.Ops) {
|
||||
p.add(o, false)
|
||||
}
|
||||
|
||||
func (p Op) add(o *op.Ops, push bool) {
|
||||
str := p.stroke
|
||||
path := p.path
|
||||
outline := p.outline
|
||||
@@ -76,6 +98,15 @@ func (p Op) Add(o *op.Ops) {
|
||||
if outline {
|
||||
data[17] = byte(1)
|
||||
}
|
||||
if push {
|
||||
data[18] = byte(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (s Stack) Pop() {
|
||||
s.ops.PopOp(op.ClipStack, s.id, s.macroID)
|
||||
data := s.ops.Write(opconst.TypePopClipLen)
|
||||
data[0] = byte(opconst.TypePopClip)
|
||||
}
|
||||
|
||||
func (p Op) approximateStroke(o *op.Ops) PathSpec {
|
||||
|
||||
@@ -20,3 +20,15 @@ func TestOpenPathOutlinePanic(t *testing.T) {
|
||||
p.Line(f32.Pt(10, 10))
|
||||
Outline{Path: p.End()}.Op()
|
||||
}
|
||||
|
||||
func TestTransformChecks(t *testing.T) {
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Error("cross-macro Pop didn't panic")
|
||||
}
|
||||
}()
|
||||
var ops op.Ops
|
||||
st := Op{}.Push(&ops)
|
||||
op.Record(&ops)
|
||||
st.Pop()
|
||||
}
|
||||
|
||||
+5
-7
@@ -4,13 +4,11 @@
|
||||
Package clip provides operations for clipping paint operations.
|
||||
Drawing outside the current clip area is ignored.
|
||||
|
||||
The current clip is initially the infinite set. An Op sets the clip
|
||||
to the intersection of the current clip and the clip area it
|
||||
represents. If you need to reset the current clip to its value
|
||||
before applying an Op, use op.StackOp.
|
||||
The current clip is initially the infinite set. Pushing and Op sets the clip
|
||||
to the intersection of the current clip and pushed clip area. Popping the
|
||||
area restores the clip to its state before pushing.
|
||||
|
||||
General clipping areas are constructed with Path. Simpler special
|
||||
cases such as rectangular clip areas also exist as convenient
|
||||
constructors.
|
||||
General clipping areas are constructed with Path. Common cases such as
|
||||
rectangular clip areas also exist as convenient constructors.
|
||||
*/
|
||||
package clip
|
||||
|
||||
+24
-3
@@ -23,7 +23,14 @@ func (r Rect) Op() Op {
|
||||
}
|
||||
}
|
||||
|
||||
// Add the clip operation.
|
||||
// Push the clip operation on the clip stack.
|
||||
func (r Rect) Push(ops *op.Ops) Stack {
|
||||
return r.Op().Push(ops)
|
||||
}
|
||||
|
||||
// Add the rectangle clip to the clip state.
|
||||
//
|
||||
// Deprecated: use Push instead.
|
||||
func (r Rect) Add(ops *op.Ops) {
|
||||
r.Op().Add(ops)
|
||||
}
|
||||
@@ -66,7 +73,14 @@ func (rr RRect) Op(ops *op.Ops) Op {
|
||||
return Outline{Path: rr.Path(ops)}.Op()
|
||||
}
|
||||
|
||||
// Add the rectangle clip.
|
||||
// Push the rectangle clip on the clip stack.
|
||||
func (rr RRect) Push(ops *op.Ops) Stack {
|
||||
return rr.Op(ops).Push(ops)
|
||||
}
|
||||
|
||||
// Add the rectangle clip to the clip state.
|
||||
//
|
||||
// Deprecated: use Push instead.
|
||||
func (rr RRect) Add(ops *op.Ops) {
|
||||
rr.Op(ops).Add(ops)
|
||||
}
|
||||
@@ -119,7 +133,14 @@ func (c Circle) Op(ops *op.Ops) Op {
|
||||
return Outline{Path: c.Path(ops)}.Op()
|
||||
}
|
||||
|
||||
// Add the circle clip.
|
||||
// Push the circle clip on the clip stack.
|
||||
func (c Circle) Push(ops *op.Ops) Stack {
|
||||
return c.Op(ops).Push(ops)
|
||||
}
|
||||
|
||||
// Add the circle clip to the clip state.
|
||||
//
|
||||
// Deprecated: use Push instead.
|
||||
func (c Circle) Add(ops *op.Ops) {
|
||||
c.Op(ops).Add(ops)
|
||||
}
|
||||
|
||||
@@ -30,24 +30,24 @@ Drawing a colored square:
|
||||
|
||||
State
|
||||
|
||||
An Ops list can be viewed as a very simple virtual machine: it has an implicit
|
||||
mutable state stack and execution flow can be controlled with macros.
|
||||
An Ops list can be viewed as a very simple virtual machine: it has state such
|
||||
as transformation and color and execution flow can be controlled with macros.
|
||||
|
||||
The Save function saves the current state for later restoring:
|
||||
Some state, such as the current color, is modified directly by operations with
|
||||
Add methods. Other state, such as transformation and clip shape, are
|
||||
represented by stacks.
|
||||
|
||||
This example sets the simple color state and pushes an offset to the
|
||||
transformation stack.
|
||||
|
||||
ops := new(op.Ops)
|
||||
// Save the current state, in particular the transform.
|
||||
state := op.Save(ops)
|
||||
// Apply a transform to subsequent operations.
|
||||
op.Offset(...).Add(ops)
|
||||
// Set the color.
|
||||
paint.ColorOp{...}.Add(ops)
|
||||
// Apply an offset to subsequent operations.
|
||||
stack := op.Offset(...).Push(ops)
|
||||
...
|
||||
// Restore the previous transform.
|
||||
state.Load()
|
||||
|
||||
You can also use this one-line to save the current state and restore it at the
|
||||
end of a function :
|
||||
|
||||
defer op.Save(ops).Load()
|
||||
// Undo the offset transformation.
|
||||
stack.Pop()
|
||||
|
||||
The MacroOp records a list of operations to be executed later:
|
||||
|
||||
@@ -67,6 +67,7 @@ package op
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
@@ -89,11 +90,20 @@ type Ops struct {
|
||||
nextStateID int
|
||||
|
||||
macroStack stack
|
||||
stacks [3]stack
|
||||
}
|
||||
|
||||
// StateOp represents a saved operation snapshop to be restored
|
||||
type StackKind uint8
|
||||
|
||||
const (
|
||||
ClipStack StackKind = iota
|
||||
AreaStack
|
||||
TransStack
|
||||
)
|
||||
|
||||
// stateOp represents a saved operation snapshop to be restored
|
||||
// later.
|
||||
type StateOp struct {
|
||||
type stateOp struct {
|
||||
id int
|
||||
macroID int
|
||||
ops *Ops
|
||||
@@ -102,7 +112,7 @@ type StateOp struct {
|
||||
// MacroOp records a list of operations for later use.
|
||||
type MacroOp struct {
|
||||
ops *Ops
|
||||
id stackID
|
||||
id StackID
|
||||
pc pc
|
||||
}
|
||||
|
||||
@@ -119,20 +129,27 @@ type InvalidateOp struct {
|
||||
At time.Time
|
||||
}
|
||||
|
||||
// TransformOp applies a transform to the current transform. The zero value
|
||||
// for TransformOp represents the identity transform.
|
||||
// TransformOp represents a transformation that can be pushed on the
|
||||
// transformation stack.
|
||||
type TransformOp struct {
|
||||
t f32.Affine2D
|
||||
}
|
||||
|
||||
// stack tracks the integer identities of MacroOp
|
||||
// operations to ensure correct pairing of Record/End.
|
||||
// TransformStack represents a TransformOp pushed on the transformation stack.
|
||||
type TransformStack struct {
|
||||
id StackID
|
||||
macroID int
|
||||
ops *Ops
|
||||
}
|
||||
|
||||
// stack tracks the integer identities of stack operations to ensure correct
|
||||
// pairing of their push and pop methods.
|
||||
type stack struct {
|
||||
currentID int
|
||||
nextID int
|
||||
}
|
||||
|
||||
type stackID struct {
|
||||
type StackID struct {
|
||||
id int
|
||||
prev int
|
||||
}
|
||||
@@ -142,22 +159,21 @@ type pc struct {
|
||||
refs int
|
||||
}
|
||||
|
||||
// Defer executes c after all other operations have completed,
|
||||
// including previously deferred operations.
|
||||
// Defer saves the current transformation and restores it prior
|
||||
// to execution. All other operation state is reset.
|
||||
// Defer executes c after all other operations have completed, including
|
||||
// previously deferred operations.
|
||||
// Defer saves the transformation stack and pushes it prior to executing
|
||||
// c. All other operation state is reset.
|
||||
//
|
||||
// Note that deferred operations are executed in first-in-first-out
|
||||
// order, unlike the Go facility of the same name.
|
||||
// Note that deferred operations are executed in first-in-first-out order,
|
||||
// unlike the Go facility of the same name.
|
||||
func Defer(o *Ops, c CallOp) {
|
||||
if c.ops == nil {
|
||||
return
|
||||
}
|
||||
state := Save(o)
|
||||
state := save(o)
|
||||
// Wrap c in a macro that loads the saved state before execution.
|
||||
m := Record(o)
|
||||
load(o, opconst.InitialStateID, opconst.AllState)
|
||||
load(o, state.id, opconst.TransformState)
|
||||
state.load()
|
||||
c.Add(o)
|
||||
c = m.Stop()
|
||||
// A Defer is recorded as a TypeDefer followed by the
|
||||
@@ -167,52 +183,80 @@ func Defer(o *Ops, c CallOp) {
|
||||
c.Add(o)
|
||||
}
|
||||
|
||||
// Save the current operations state.
|
||||
func Save(o *Ops) StateOp {
|
||||
type SaveStack struct {
|
||||
ops *Ops
|
||||
clip struct {
|
||||
id StackID
|
||||
macroID int
|
||||
}
|
||||
trans TransformStack
|
||||
}
|
||||
|
||||
// Deprecated: use state-specific stack operations instead (TransformOp.Push
|
||||
// etc.).
|
||||
func Save(o *Ops) SaveStack {
|
||||
st := SaveStack{
|
||||
ops: o,
|
||||
trans: Offset(f32.Point{}).Push(o),
|
||||
}
|
||||
const inf = 1e6
|
||||
bounds := image.Rectangle{Min: image.Pt(-inf, -inf), Max: image.Pt(inf, inf)}
|
||||
{
|
||||
st.clip.id, st.clip.macroID = o.PushOp(ClipStack)
|
||||
// Push clip stack with no-op (infinite) clipping rect. Copied from clip.Op.Push.
|
||||
bo := binary.LittleEndian
|
||||
data := o.Write(opconst.TypeClipLen)
|
||||
data[0] = byte(opconst.TypeClip)
|
||||
bo.PutUint32(data[1:], uint32(bounds.Min.X))
|
||||
bo.PutUint32(data[5:], uint32(bounds.Min.Y))
|
||||
bo.PutUint32(data[9:], uint32(bounds.Max.X))
|
||||
bo.PutUint32(data[13:], uint32(bounds.Max.Y))
|
||||
data[17] = byte(1) // Outline
|
||||
data[18] = byte(1) // Push
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func (s SaveStack) Load() {
|
||||
// Pop clip.
|
||||
s.ops.PopOp(ClipStack, s.clip.id, s.clip.macroID)
|
||||
data := s.ops.Write(opconst.TypePopClipLen)
|
||||
data[0] = byte(opconst.TypePopClip)
|
||||
|
||||
s.trans.Pop()
|
||||
}
|
||||
|
||||
// save the effective transformation.
|
||||
func save(o *Ops) stateOp {
|
||||
o.nextStateID++
|
||||
s := StateOp{
|
||||
s := stateOp{
|
||||
ops: o,
|
||||
id: o.nextStateID,
|
||||
macroID: o.macroStack.currentID,
|
||||
}
|
||||
save(o, s.id)
|
||||
return s
|
||||
}
|
||||
|
||||
// save records a save of the operations state to
|
||||
// id.
|
||||
func save(o *Ops, id int) {
|
||||
bo := binary.LittleEndian
|
||||
data := o.Write(opconst.TypeSaveLen)
|
||||
data[0] = byte(opconst.TypeSave)
|
||||
bo.PutUint32(data[1:], uint32(id))
|
||||
}
|
||||
|
||||
// Load a previously saved operations state.
|
||||
func (s StateOp) Load() {
|
||||
if s.ops.macroStack.currentID != s.macroID {
|
||||
panic("load in a different macro than save")
|
||||
}
|
||||
if s.id == 0 {
|
||||
panic("zero-value op")
|
||||
}
|
||||
load(s.ops, s.id, opconst.AllState)
|
||||
bo.PutUint32(data[1:], uint32(s.id))
|
||||
return s
|
||||
}
|
||||
|
||||
// load a previously saved operations state given
|
||||
// its ID. Only state included in mask is affected.
|
||||
func load(o *Ops, id int, m opconst.StateMask) {
|
||||
// its ID.
|
||||
func (s stateOp) load() {
|
||||
bo := binary.LittleEndian
|
||||
data := o.Write(opconst.TypeLoadLen)
|
||||
data := s.ops.Write(opconst.TypeLoadLen)
|
||||
data[0] = byte(opconst.TypeLoad)
|
||||
data[1] = byte(m)
|
||||
bo.PutUint32(data[2:], uint32(id))
|
||||
bo.PutUint32(data[1:], uint32(s.id))
|
||||
}
|
||||
|
||||
// Reset the Ops, preparing it for re-use. Reset invalidates
|
||||
// any recorded macros.
|
||||
func (o *Ops) Reset() {
|
||||
o.macroStack = stack{}
|
||||
for i := range o.stacks {
|
||||
o.stacks[i] = stack{}
|
||||
}
|
||||
// Leave references to the GC.
|
||||
for i := range o.refs {
|
||||
o.refs[i] = nil
|
||||
@@ -244,6 +288,17 @@ func (o *Ops) Write(n int) []byte {
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func (o *Ops) PushOp(kind StackKind) (StackID, int) {
|
||||
return o.stacks[kind].push(), o.macroStack.currentID
|
||||
}
|
||||
|
||||
func (o *Ops) PopOp(kind StackKind, sid StackID, macroID int) {
|
||||
if o.macroStack.currentID != macroID {
|
||||
panic("stack push and pop must not cross macro boundary")
|
||||
}
|
||||
o.stacks[kind].pop(sid)
|
||||
}
|
||||
|
||||
// Write1 is for internal use only.
|
||||
func (o *Ops) Write1(n int, ref1 interface{}) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
@@ -334,22 +389,45 @@ func Affine(a f32.Affine2D) TransformOp {
|
||||
return TransformOp{t: a}
|
||||
}
|
||||
|
||||
func (t TransformOp) Add(o *Ops) {
|
||||
data := o.Write(opconst.TypeTransformLen)
|
||||
data[0] = byte(opconst.TypeTransform)
|
||||
bo := binary.LittleEndian
|
||||
a, b, c, d, e, f := t.t.Elems()
|
||||
bo.PutUint32(data[1:], math.Float32bits(a))
|
||||
bo.PutUint32(data[1+4*1:], math.Float32bits(b))
|
||||
bo.PutUint32(data[1+4*2:], math.Float32bits(c))
|
||||
bo.PutUint32(data[1+4*3:], math.Float32bits(d))
|
||||
bo.PutUint32(data[1+4*4:], math.Float32bits(e))
|
||||
bo.PutUint32(data[1+4*5:], math.Float32bits(f))
|
||||
// Push the current transformation to the stack and then multiply the
|
||||
// current transformation with t.
|
||||
func (t TransformOp) Push(o *Ops) TransformStack {
|
||||
id, macroID := o.PushOp(TransStack)
|
||||
t.add(o, true)
|
||||
return TransformStack{ops: o, id: id, macroID: macroID}
|
||||
}
|
||||
|
||||
func (s *stack) push() stackID {
|
||||
// Add is like Push except it doesn't push the current transformation to the
|
||||
// stack.
|
||||
func (t TransformOp) Add(o *Ops) {
|
||||
t.add(o, false)
|
||||
}
|
||||
|
||||
func (t TransformOp) add(o *Ops, push bool) {
|
||||
data := o.Write(opconst.TypeTransformLen)
|
||||
data[0] = byte(opconst.TypeTransform)
|
||||
if push {
|
||||
data[1] = 1
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
a, b, c, d, e, f := t.t.Elems()
|
||||
bo.PutUint32(data[2:], math.Float32bits(a))
|
||||
bo.PutUint32(data[2+4*1:], math.Float32bits(b))
|
||||
bo.PutUint32(data[2+4*2:], math.Float32bits(c))
|
||||
bo.PutUint32(data[2+4*3:], math.Float32bits(d))
|
||||
bo.PutUint32(data[2+4*4:], math.Float32bits(e))
|
||||
bo.PutUint32(data[2+4*5:], math.Float32bits(f))
|
||||
}
|
||||
|
||||
func (t TransformStack) Pop() {
|
||||
t.ops.PopOp(TransStack, t.id, t.macroID)
|
||||
data := t.ops.Write(opconst.TypePopTransformLen)
|
||||
data[0] = byte(opconst.TypePopTransform)
|
||||
}
|
||||
|
||||
func (s *stack) push() StackID {
|
||||
s.nextID++
|
||||
sid := stackID{
|
||||
sid := StackID{
|
||||
id: s.nextID,
|
||||
prev: s.currentID,
|
||||
}
|
||||
@@ -357,13 +435,13 @@ func (s *stack) push() stackID {
|
||||
return sid
|
||||
}
|
||||
|
||||
func (s *stack) check(sid stackID) {
|
||||
func (s *stack) check(sid StackID) {
|
||||
if s.currentID != sid.id {
|
||||
panic("unbalanced operation")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) pop(sid stackID) {
|
||||
func (s *stack) pop(sid StackID) {
|
||||
s.check(sid)
|
||||
s.currentID = sid.prev
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package op
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gioui.org/f32"
|
||||
)
|
||||
|
||||
func TestTransformChecks(t *testing.T) {
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Error("cross-macro Pop didn't panic")
|
||||
}
|
||||
}()
|
||||
var ops Ops
|
||||
trans := Offset(f32.Point{}).Push(&ops)
|
||||
Record(&ops)
|
||||
trans.Pop()
|
||||
}
|
||||
+1
-3
@@ -136,8 +136,7 @@ func (d PaintOp) Add(o *op.Ops) {
|
||||
|
||||
// FillShape fills the clip shape with a color.
|
||||
func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) {
|
||||
defer op.Save(ops).Load()
|
||||
shape.Add(ops)
|
||||
defer shape.Push(ops).Pop()
|
||||
Fill(ops, c)
|
||||
}
|
||||
|
||||
@@ -146,7 +145,6 @@ func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) {
|
||||
// the painted area. Use FillShape unless you need to paint several
|
||||
// times within the same clip.Op.
|
||||
func Fill(ops *op.Ops, c color.NRGBA) {
|
||||
defer op.Save(ops).Load()
|
||||
ColorOp{Color: c}.Add(ops)
|
||||
PaintOp{}.Add(ops)
|
||||
}
|
||||
|
||||
+1
-4
@@ -11,7 +11,6 @@ import (
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Clickable represents a clickable area.
|
||||
@@ -93,10 +92,8 @@ func (b *Clickable) History() []Press {
|
||||
// Layout and update the button state
|
||||
func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions {
|
||||
b.update(gtx)
|
||||
stack := op.Save(gtx.Ops)
|
||||
pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
|
||||
defer pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
|
||||
b.click.Add(gtx.Ops)
|
||||
stack.Load()
|
||||
for len(b.history) > 0 {
|
||||
c := b.history[0]
|
||||
if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second {
|
||||
|
||||
+12
-15
@@ -547,7 +547,7 @@ func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
|
||||
r.Min.Y -= pointerPadding
|
||||
r.Max.X += pointerPadding
|
||||
r.Max.X += pointerPadding
|
||||
pointer.Rect(r).Add(gtx.Ops)
|
||||
defer pointer.Rect(r).Push(gtx.Ops).Pop()
|
||||
pointer.CursorNameOp{Name: pointer.CursorText}.Add(gtx.Ops)
|
||||
|
||||
var scrollRange image.Rectangle
|
||||
@@ -583,31 +583,31 @@ func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
|
||||
func (e *Editor) PaintSelection(gtx layout.Context) {
|
||||
cl := textPadding(e.lines)
|
||||
cl.Max = cl.Max.Add(e.viewSize)
|
||||
clip.Rect(cl).Add(gtx.Ops)
|
||||
defer clip.Rect(cl).Push(gtx.Ops).Pop()
|
||||
for _, shape := range e.shapes {
|
||||
if !shape.selected {
|
||||
continue
|
||||
}
|
||||
stack := op.Save(gtx.Ops)
|
||||
offset := shape.offset
|
||||
offset.Y += shape.selectionYOffs
|
||||
op.Offset(layout.FPt(offset)).Add(gtx.Ops)
|
||||
clip.Rect(image.Rectangle{Max: shape.selectionSize}).Add(gtx.Ops)
|
||||
t := op.Offset(layout.FPt(offset)).Push(gtx.Ops)
|
||||
cl := clip.Rect(image.Rectangle{Max: shape.selectionSize}).Push(gtx.Ops)
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
stack.Load()
|
||||
cl.Pop()
|
||||
t.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Editor) PaintText(gtx layout.Context) {
|
||||
cl := textPadding(e.lines)
|
||||
cl.Max = cl.Max.Add(e.viewSize)
|
||||
clip.Rect(cl).Add(gtx.Ops)
|
||||
defer clip.Rect(cl).Push(gtx.Ops).Pop()
|
||||
for _, shape := range e.shapes {
|
||||
stack := op.Save(gtx.Ops)
|
||||
op.Offset(layout.FPt(shape.offset)).Add(gtx.Ops)
|
||||
shape.clip.Add(gtx.Ops)
|
||||
t := op.Offset(layout.FPt(shape.offset)).Push(gtx.Ops)
|
||||
cl := shape.clip.Push(gtx.Ops)
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
stack.Load()
|
||||
cl.Pop()
|
||||
t.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +620,6 @@ func (e *Editor) PaintCaret(gtx layout.Context) {
|
||||
carX := e.caret.start.x
|
||||
carY := e.caret.start.y
|
||||
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
carX -= carWidth / 2
|
||||
carAsc, carDesc := -e.lines[e.caret.start.lineCol.Y].Bounds.Min.Y, e.lines[e.caret.start.lineCol.Y].Bounds.Max.Y
|
||||
carRect := image.Rectangle{
|
||||
@@ -643,10 +642,8 @@ func (e *Editor) PaintCaret(gtx layout.Context) {
|
||||
cl.Max = cl.Max.Add(e.viewSize)
|
||||
carRect = cl.Intersect(carRect)
|
||||
if !carRect.Empty() {
|
||||
st := op.Save(gtx.Ops)
|
||||
clip.Rect(carRect).Add(gtx.Ops)
|
||||
defer clip.Rect(carRect).Push(gtx.Ops).Pop()
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
st.Load()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -6,7 +6,6 @@ import (
|
||||
"gioui.org/gesture"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
type Enum struct {
|
||||
@@ -44,8 +43,7 @@ func (e *Enum) Hovered() (string, bool) {
|
||||
|
||||
// Layout adds the event handler for key.
|
||||
func (e *Enum) Layout(gtx layout.Context, key string) layout.Dimensions {
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
|
||||
defer pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
|
||||
|
||||
if index(e.values, key) == -1 {
|
||||
e.values = append(e.values, key)
|
||||
|
||||
@@ -33,7 +33,7 @@ func ExampleClickable_passthrough() {
|
||||
// button2 completely covers button1, but pass-through allows pointer
|
||||
// events to pass through to button1.
|
||||
area.PassThrough = true
|
||||
area.Add(gtx.Ops)
|
||||
defer area.Push(gtx.Ops).Pop()
|
||||
button2.Layout(gtx)
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -8,7 +8,6 @@ import (
|
||||
"gioui.org/gesture"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Float is for selecting a value in a range.
|
||||
@@ -59,13 +58,12 @@ func (f *Float) Layout(gtx layout.Context, pointerMargin int, min, max float32)
|
||||
f.pos = 1
|
||||
}
|
||||
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
margin := f.Axis.Convert(image.Pt(pointerMargin, 0))
|
||||
rect := image.Rectangle{
|
||||
Min: margin.Mul(-1),
|
||||
Max: size.Add(margin),
|
||||
}
|
||||
pointer.Rect(rect).Add(gtx.Ops)
|
||||
defer pointer.Rect(rect).Push(gtx.Ops).Pop()
|
||||
f.drag.Add(gtx.Ops)
|
||||
|
||||
return layout.Dimensions{Size: size}
|
||||
|
||||
+1
-3
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/unit"
|
||||
@@ -43,8 +42,7 @@ func (ic *Icon) Layout(gtx layout.Context, color color.NRGBA) layout.Dimensions
|
||||
sz = gtx.Metric.Px(defaultIconSize)
|
||||
}
|
||||
size := gtx.Constraints.Constrain(image.Pt(sz, sz))
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
clip.Rect{Max: size}.Add(gtx.Ops)
|
||||
defer clip.Rect{Max: size}.Push(gtx.Ops).Pop()
|
||||
|
||||
ico := ic.image(size.X, color)
|
||||
ico.Add(gtx.Ops)
|
||||
|
||||
+2
-4
@@ -32,8 +32,6 @@ type Image struct {
|
||||
const defaultScale = float32(160.0 / 72.0)
|
||||
|
||||
func (im Image) Layout(gtx layout.Context) layout.Dimensions {
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
|
||||
scale := im.Scale
|
||||
if scale == 0 {
|
||||
scale = defaultScale
|
||||
@@ -44,11 +42,11 @@ func (im Image) Layout(gtx layout.Context) layout.Dimensions {
|
||||
w, h := gtx.Px(unit.Dp(wf*scale)), gtx.Px(unit.Dp(hf*scale))
|
||||
|
||||
dims, trans := im.Fit.scale(gtx.Constraints, im.Position, layout.Dimensions{Size: image.Pt(w, h)})
|
||||
clip.Rect{Max: dims.Size}.Add(gtx.Ops)
|
||||
defer clip.Rect{Max: dims.Size}.Push(gtx.Ops).Pop()
|
||||
|
||||
pixelScale := scale * gtx.Metric.PxPerDp
|
||||
trans = trans.Mul(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(pixelScale, pixelScale)))
|
||||
op.Affine(trans).Add(gtx.Ops)
|
||||
defer op.Affine(trans).Push(gtx.Ops).Pop()
|
||||
|
||||
im.Src.Add(gtx.Ops)
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
|
||||
+6
-5
@@ -176,12 +176,13 @@ func (l Label) Layout(gtx layout.Context, s text.Shaper, font text.Font, size un
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
stack := op.Save(gtx.Ops)
|
||||
op.Offset(layout.FPt(off)).Add(gtx.Ops)
|
||||
clip.Rect(cl.Sub(off)).Add(gtx.Ops)
|
||||
s.Shape(font, textSize, l).Add(gtx.Ops)
|
||||
t := op.Offset(layout.FPt(off)).Push(gtx.Ops)
|
||||
rcl := clip.Rect(cl.Sub(off)).Push(gtx.Ops)
|
||||
cl := s.Shape(font, textSize, l).Push(gtx.Ops)
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
stack.Load()
|
||||
cl.Pop()
|
||||
rcl.Pop()
|
||||
t.Pop()
|
||||
}
|
||||
return dims
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) la
|
||||
return layout.Stack{}.Layout(gtx,
|
||||
layout.Expanded(button.Layout),
|
||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
||||
clip.Rect{Max: gtx.Constraints.Min}.Add(gtx.Ops)
|
||||
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
||||
for _, c := range button.History() {
|
||||
drawInk(gtx, c)
|
||||
}
|
||||
@@ -118,10 +118,10 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di
|
||||
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
||||
rr := float32(gtx.Px(b.CornerRadius))
|
||||
clip.UniformRRect(f32.Rectangle{Max: f32.Point{
|
||||
defer clip.UniformRRect(f32.Rectangle{Max: f32.Point{
|
||||
X: float32(gtx.Constraints.Min.X),
|
||||
Y: float32(gtx.Constraints.Min.Y),
|
||||
}}, rr).Add(gtx.Ops)
|
||||
}}, rr).Push(gtx.Ops).Pop()
|
||||
background := b.Background
|
||||
switch {
|
||||
case gtx.Queue == nil:
|
||||
@@ -149,9 +149,9 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
sizex, sizey := gtx.Constraints.Min.X, gtx.Constraints.Min.Y
|
||||
sizexf, sizeyf := float32(sizex), float32(sizey)
|
||||
rr := (sizexf + sizeyf) * .25
|
||||
clip.UniformRRect(f32.Rectangle{
|
||||
defer clip.UniformRRect(f32.Rectangle{
|
||||
Max: f32.Point{X: sizexf, Y: sizeyf},
|
||||
}, rr).Add(gtx.Ops)
|
||||
}, rr).Push(gtx.Ops).Pop()
|
||||
background := b.Background
|
||||
switch {
|
||||
case gtx.Queue == nil:
|
||||
@@ -178,7 +178,7 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
})
|
||||
}),
|
||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
||||
pointer.Ellipse(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
|
||||
defer pointer.Ellipse(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
|
||||
return b.Button.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
@@ -272,15 +272,14 @@ func drawInk(gtx layout.Context, c widget.Press) {
|
||||
alpha := 0.7 * alphaBezier
|
||||
const col = 0.8
|
||||
ba, bc := byte(alpha*0xff), byte(col*0xff)
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
rgba := f32color.MulAlpha(color.NRGBA{A: 0xff, R: bc, G: bc, B: bc}, ba)
|
||||
ink := paint.ColorOp{Color: rgba}
|
||||
ink.Add(gtx.Ops)
|
||||
rr := size * .5
|
||||
op.Offset(c.Position.Add(f32.Point{
|
||||
defer op.Offset(c.Position.Add(f32.Point{
|
||||
X: -rr,
|
||||
Y: -rr,
|
||||
})).Add(gtx.Ops)
|
||||
clip.UniformRRect(f32.Rectangle{Max: f32.Pt(size, size)}, rr).Add(gtx.Ops)
|
||||
})).Push(gtx.Ops).Pop()
|
||||
defer clip.UniformRRect(f32.Rectangle{Max: f32.Pt(size, size)}, rr).Push(gtx.Ops).Pop()
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/op/paint"
|
||||
@@ -84,6 +83,5 @@ func (c *checkable) layout(gtx layout.Context, checked, hovered bool) layout.Dim
|
||||
})
|
||||
}),
|
||||
)
|
||||
pointer.Rect(image.Rectangle{Max: dims.Size}).Add(gtx.Ops)
|
||||
return dims
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
package material
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
@@ -32,6 +35,7 @@ func CheckBox(th *Theme, checkBox *widget.Bool, label string) CheckBoxStyle {
|
||||
// Layout updates the checkBox and displays it.
|
||||
func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
dims := c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered())
|
||||
defer pointer.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
|
||||
gtx.Constraints.Min = dims.Size
|
||||
c.CheckBox.Layout(gtx)
|
||||
return dims
|
||||
|
||||
@@ -43,7 +43,6 @@ func Editor(th *Theme, editor *widget.Editor, hint string) EditorStyle {
|
||||
}
|
||||
|
||||
func (e EditorStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
macro := op.Record(gtx.Ops)
|
||||
paint.ColorOp{Color: e.HintColor}.Add(gtx.Ops)
|
||||
var maxlines int
|
||||
|
||||
@@ -156,16 +156,14 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta
|
||||
Max: gtx.Constraints.Min,
|
||||
}
|
||||
pointerArea := pointer.Rect(area)
|
||||
pointerArea.Add(gtx.Ops)
|
||||
defer pointerArea.Push(gtx.Ops).Pop()
|
||||
s.Scrollbar.AddDrag(gtx.Ops)
|
||||
|
||||
// Stack a normal clickable area on top of the draggable area
|
||||
// to capture non-dragging clicks.
|
||||
saved := op.Save(gtx.Ops)
|
||||
pointerArea.PassThrough = true
|
||||
pointerArea.Add(gtx.Ops)
|
||||
defer pointerArea.Push(gtx.Ops).Pop()
|
||||
s.Scrollbar.AddTrack(gtx.Ops)
|
||||
saved.Load()
|
||||
|
||||
paint.FillShape(gtx.Ops, s.Track.Color, clip.Rect(area).Op())
|
||||
return layout.Dimensions{}
|
||||
@@ -194,9 +192,8 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta
|
||||
radius := float32(gtx.Px(s.Indicator.CornerRadius))
|
||||
|
||||
// Lay out the indicator.
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
offset := axis.Convert(image.Pt(viewStart, 0))
|
||||
op.Offset(layout.FPt(offset)).Add(gtx.Ops)
|
||||
defer op.Offset(layout.FPt(offset)).Push(gtx.Ops).Pop()
|
||||
paint.FillShape(gtx.Ops, s.Indicator.Color, clip.RRect{
|
||||
Rect: f32.Rectangle{
|
||||
Max: indicatorDimsF,
|
||||
@@ -210,7 +207,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
|
||||
area.Add(gtx.Ops)
|
||||
defer area.Push(gtx.Ops).Pop()
|
||||
s.Scrollbar.AddIndicator(gtx.Ops)
|
||||
|
||||
return layout.Dimensions{Size: axis.Convert(gtx.Constraints.Min)}
|
||||
|
||||
@@ -36,18 +36,17 @@ func (l LoaderStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
sz := gtx.Constraints.Constrain(image.Pt(diam, diam))
|
||||
radius := float32(sz.X) * .5
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
op.Offset(f32.Pt(radius, radius)).Add(gtx.Ops)
|
||||
defer op.Offset(f32.Pt(radius, radius)).Push(gtx.Ops).Pop()
|
||||
|
||||
dt := float32((time.Duration(gtx.Now.UnixNano()) % (time.Second)).Seconds())
|
||||
startAngle := dt * math.Pi * 2
|
||||
endAngle := startAngle + math.Pi*1.5
|
||||
|
||||
clipLoader(gtx.Ops, startAngle, endAngle, radius)
|
||||
defer clipLoader(gtx.Ops, startAngle, endAngle, radius).Push(gtx.Ops).Pop()
|
||||
paint.ColorOp{
|
||||
Color: l.Color,
|
||||
}.Add(gtx.Ops)
|
||||
op.Offset(f32.Pt(-radius, -radius)).Add(gtx.Ops)
|
||||
defer op.Offset(f32.Pt(-radius, -radius)).Push(gtx.Ops).Pop()
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
op.InvalidateOp{}.Add(gtx.Ops)
|
||||
return layout.Dimensions{
|
||||
@@ -55,7 +54,7 @@ func (l LoaderStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
}
|
||||
|
||||
func clipLoader(ops *op.Ops, startAngle, endAngle, radius float32) {
|
||||
func clipLoader(ops *op.Ops, startAngle, endAngle, radius float32) clip.Op {
|
||||
const thickness = .25
|
||||
|
||||
var (
|
||||
@@ -74,11 +73,11 @@ func clipLoader(ops *op.Ops, startAngle, endAngle, radius float32) {
|
||||
p.Begin(ops)
|
||||
p.Move(pen)
|
||||
p.Arc(center, center, delta)
|
||||
clip.Stroke{
|
||||
return clip.Stroke{
|
||||
Path: p.End(),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: width,
|
||||
Cap: clip.FlatCap,
|
||||
},
|
||||
}.Op().Add(ops)
|
||||
}.Op()
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
d := image.Point{X: int(width), Y: gtx.Px(maxHeight)}
|
||||
|
||||
height := float32(gtx.Px(maxHeight))
|
||||
clip.UniformRRect(f32.Rectangle{Max: f32.Pt(width, height)}, rr).Add(gtx.Ops)
|
||||
defer clip.UniformRRect(f32.Rectangle{Max: f32.Pt(width, height)}, rr).Push(gtx.Ops).Pop()
|
||||
paint.ColorOp{Color: color}.Add(gtx.Ops)
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
|
||||
|
||||
@@ -36,14 +36,13 @@ func (p ProgressCircleStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
sz := gtx.Constraints.Constrain(image.Pt(diam, diam))
|
||||
radius := float32(sz.X) * .5
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
op.Offset(f32.Pt(radius, radius)).Add(gtx.Ops)
|
||||
defer op.Offset(f32.Pt(radius, radius)).Push(gtx.Ops).Pop()
|
||||
|
||||
clipLoader(gtx.Ops, -math.Pi/2, -math.Pi/2+math.Pi*2*p.Progress, radius)
|
||||
defer clipLoader(gtx.Ops, -math.Pi/2, -math.Pi/2+math.Pi*2*p.Progress, radius).Push(gtx.Ops).Pop()
|
||||
paint.ColorOp{
|
||||
Color: p.Color,
|
||||
}.Add(gtx.Ops)
|
||||
op.Offset(f32.Pt(-radius, -radius)).Add(gtx.Ops)
|
||||
defer op.Offset(f32.Pt(-radius, -radius)).Push(gtx.Ops).Pop()
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
return layout.Dimensions{
|
||||
Size: sz,
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
package material
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
@@ -38,6 +41,7 @@ func RadioButton(th *Theme, group *widget.Enum, key, label string) RadioButtonSt
|
||||
func (r RadioButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
hovered, hovering := r.Group.Hovered()
|
||||
dims := r.layout(gtx, r.Group.Value == r.Key, hovering && hovered == r.Key)
|
||||
defer pointer.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
|
||||
gtx.Constraints.Min = dims.Size
|
||||
r.Group.Layout(gtx, r.Key)
|
||||
return dims
|
||||
|
||||
@@ -49,14 +49,12 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
sizeCross := max(2*thumbRadius, touchSizePx)
|
||||
size := axis.Convert(image.Pt(sizeMain, sizeCross))
|
||||
|
||||
st := op.Save(gtx.Ops)
|
||||
o := axis.Convert(image.Pt(thumbRadius, 0))
|
||||
op.Offset(layout.FPt(o)).Add(gtx.Ops)
|
||||
defer op.Offset(layout.FPt(o)).Push(gtx.Ops).Pop()
|
||||
gtx.Constraints.Min = axis.Convert(image.Pt(sizeMain-2*thumbRadius, sizeCross))
|
||||
s.Float.Layout(gtx, thumbRadius, s.Min, s.Max)
|
||||
gtx.Constraints.Min = gtx.Constraints.Min.Add(axis.Convert(image.Pt(0, sizeCross)))
|
||||
thumbPos := thumbRadius + int(s.Float.Pos())
|
||||
st.Load()
|
||||
|
||||
color := s.Color
|
||||
if gtx.Queue == nil {
|
||||
@@ -64,24 +62,18 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
|
||||
// Draw track before thumb.
|
||||
st = op.Save(gtx.Ops)
|
||||
track := image.Rectangle{
|
||||
Min: axis.Convert(image.Pt(thumbRadius, sizeCross/2-trackWidth/2)),
|
||||
Max: axis.Convert(image.Pt(thumbPos, sizeCross/2+trackWidth/2)),
|
||||
}
|
||||
clip.Rect(track).Add(gtx.Ops)
|
||||
paint.Fill(gtx.Ops, color)
|
||||
st.Load()
|
||||
paint.FillShape(gtx.Ops, color, clip.Rect(track).Op())
|
||||
|
||||
// Draw track after thumb.
|
||||
st = op.Save(gtx.Ops)
|
||||
track = image.Rectangle{
|
||||
Min: axis.Convert(image.Pt(thumbPos, axis.Convert(track.Min).Y)),
|
||||
Max: axis.Convert(image.Pt(sizeMain-thumbRadius, axis.Convert(track.Max).Y)),
|
||||
}
|
||||
clip.Rect(track).Add(gtx.Ops)
|
||||
paint.Fill(gtx.Ops, f32color.MulAlpha(color, 96))
|
||||
st.Load()
|
||||
paint.FillShape(gtx.Ops, f32color.MulAlpha(color, 96), clip.Rect(track).Op())
|
||||
|
||||
// Draw thumb.
|
||||
pt := axis.Convert(image.Pt(thumbPos, sizeCross/2))
|
||||
|
||||
+13
-16
@@ -45,7 +45,6 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
trackOff := float32(thumbSize-trackHeight) * .5
|
||||
|
||||
// Draw track.
|
||||
stack := op.Save(gtx.Ops)
|
||||
trackCorner := float32(trackHeight) / 2
|
||||
trackRect := f32.Rectangle{Max: f32.Point{
|
||||
X: float32(trackWidth),
|
||||
@@ -59,33 +58,33 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
col = f32color.Disabled(col)
|
||||
}
|
||||
trackColor := s.Color.Track
|
||||
op.Offset(f32.Point{Y: trackOff}).Add(gtx.Ops)
|
||||
clip.UniformRRect(trackRect, trackCorner).Add(gtx.Ops)
|
||||
t := op.Offset(f32.Point{Y: trackOff}).Push(gtx.Ops)
|
||||
cl := clip.UniformRRect(trackRect, trackCorner).Push(gtx.Ops)
|
||||
paint.ColorOp{Color: trackColor}.Add(gtx.Ops)
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
stack.Load()
|
||||
cl.Pop()
|
||||
t.Pop()
|
||||
|
||||
// Draw thumb ink.
|
||||
stack = op.Save(gtx.Ops)
|
||||
inkSize := gtx.Px(unit.Dp(44))
|
||||
rr := float32(inkSize) * .5
|
||||
inkOff := f32.Point{
|
||||
X: float32(trackWidth)*.5 - rr,
|
||||
Y: -rr + float32(trackHeight)*.5 + trackOff,
|
||||
}
|
||||
op.Offset(inkOff).Add(gtx.Ops)
|
||||
t = op.Offset(inkOff).Push(gtx.Ops)
|
||||
gtx.Constraints.Min = image.Pt(inkSize, inkSize)
|
||||
clip.UniformRRect(f32.Rectangle{Max: layout.FPt(gtx.Constraints.Min)}, rr).Add(gtx.Ops)
|
||||
cl = clip.UniformRRect(f32.Rectangle{Max: layout.FPt(gtx.Constraints.Min)}, rr).Push(gtx.Ops)
|
||||
for _, p := range s.Switch.History() {
|
||||
drawInk(gtx, p)
|
||||
}
|
||||
stack.Load()
|
||||
cl.Pop()
|
||||
t.Pop()
|
||||
|
||||
// Compute thumb offset and color.
|
||||
stack = op.Save(gtx.Ops)
|
||||
// Compute thumb offset.
|
||||
if s.Switch.Value {
|
||||
off := trackWidth - thumbSize
|
||||
op.Offset(f32.Point{X: float32(off)}).Add(gtx.Ops)
|
||||
xoff := float32(trackWidth - thumbSize)
|
||||
defer op.Offset(f32.Point{X: xoff}).Push(gtx.Ops).Pop()
|
||||
}
|
||||
|
||||
thumbRadius := float32(thumbSize) / 2
|
||||
@@ -118,18 +117,16 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
}.Op(gtx.Ops))
|
||||
|
||||
// Set up click area.
|
||||
stack = op.Save(gtx.Ops)
|
||||
clickSize := gtx.Px(unit.Dp(40))
|
||||
clickOff := f32.Point{
|
||||
X: (float32(trackWidth) - float32(clickSize)) * .5,
|
||||
Y: (float32(trackHeight)-float32(clickSize))*.5 + trackOff,
|
||||
}
|
||||
op.Offset(clickOff).Add(gtx.Ops)
|
||||
defer op.Offset(clickOff).Push(gtx.Ops).Pop()
|
||||
sz := image.Pt(clickSize, clickSize)
|
||||
pointer.Ellipse(image.Rectangle{Max: sz}).Add(gtx.Ops)
|
||||
defer pointer.Ellipse(image.Rectangle{Max: sz}).Push(gtx.Ops).Pop()
|
||||
gtx.Constraints.Min = sz
|
||||
s.Switch.Layout(gtx)
|
||||
stack.Load()
|
||||
|
||||
dims := image.Point{X: trackWidth, Y: thumbSize}
|
||||
return layout.Dimensions{Size: dims}
|
||||
|
||||
Reference in New Issue
Block a user