chore(v2): vendor dependencies for offline/China builds
go mod vendor pins onnxruntime_go v1.12.1, Gio and the rest into v2/vendor so go run/build work without hitting proxy.golang.org (blocked/slow in China). Verified: CGO_ENABLED=1 go build -mod=vendor ./internal/spike and GOOS=windows go build -mod=vendor ./internal/ui both pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+354
@@ -0,0 +1,354 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package clip
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash/maphash"
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"gioui.org/f32"
|
||||
f32internal "gioui.org/internal/f32"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/internal/scene"
|
||||
"gioui.org/internal/stroke"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Op represents a clip area. Op intersects the current clip area with
|
||||
// itself.
|
||||
type Op struct {
|
||||
path PathSpec
|
||||
|
||||
outline bool
|
||||
width float32
|
||||
}
|
||||
|
||||
// Stack represents an Op pushed on the clip stack.
|
||||
type Stack struct {
|
||||
ops *ops.Ops
|
||||
id ops.StackID
|
||||
macroID uint32
|
||||
}
|
||||
|
||||
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 := ops.PushOp(&o.Internal, ops.ClipStack)
|
||||
p.add(o)
|
||||
return Stack{ops: &o.Internal, id: id, macroID: macroID}
|
||||
}
|
||||
|
||||
func (p Op) add(o *op.Ops) {
|
||||
path := p.path
|
||||
|
||||
if !path.hasSegments && p.width > 0 {
|
||||
switch p.path.shape {
|
||||
case ops.Rect:
|
||||
b := f32internal.FRect(path.bounds)
|
||||
var rect Path
|
||||
rect.Begin(o)
|
||||
rect.MoveTo(b.Min)
|
||||
rect.LineTo(f32.Pt(b.Max.X, b.Min.Y))
|
||||
rect.LineTo(b.Max)
|
||||
rect.LineTo(f32.Pt(b.Min.X, b.Max.Y))
|
||||
rect.Close()
|
||||
path = rect.End()
|
||||
case ops.Path:
|
||||
// Nothing to do.
|
||||
default:
|
||||
panic("invalid empty path for shape")
|
||||
}
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
if path.hasSegments {
|
||||
data := ops.Write(&o.Internal, ops.TypePathLen)
|
||||
data[0] = byte(ops.TypePath)
|
||||
bo.PutUint64(data[1:], path.hash)
|
||||
path.spec.Add(o)
|
||||
}
|
||||
|
||||
bounds := path.bounds
|
||||
if p.width > 0 {
|
||||
// Expand bounds to cover stroke.
|
||||
half := int(p.width*.5 + .5)
|
||||
bounds.Min.X -= half
|
||||
bounds.Min.Y -= half
|
||||
bounds.Max.X += half
|
||||
bounds.Max.Y += half
|
||||
data := ops.Write(&o.Internal, ops.TypeStrokeLen)
|
||||
data[0] = byte(ops.TypeStroke)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[1:], math.Float32bits(p.width))
|
||||
}
|
||||
|
||||
data := ops.Write(&o.Internal, ops.TypeClipLen)
|
||||
data[0] = byte(ops.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))
|
||||
if p.outline {
|
||||
data[17] = byte(1)
|
||||
}
|
||||
data[18] = byte(path.shape)
|
||||
}
|
||||
|
||||
func (s Stack) Pop() {
|
||||
ops.PopOp(s.ops, ops.ClipStack, s.id, s.macroID)
|
||||
data := ops.Write(s.ops, ops.TypePopClipLen)
|
||||
data[0] = byte(ops.TypePopClip)
|
||||
}
|
||||
|
||||
type PathSpec struct {
|
||||
spec op.CallOp
|
||||
// hasSegments tracks whether there are any segments in the path.
|
||||
hasSegments bool
|
||||
bounds image.Rectangle
|
||||
shape ops.Shape
|
||||
hash uint64
|
||||
}
|
||||
|
||||
// Path constructs a Op clip path described by lines and
|
||||
// Bézier curves, where drawing outside the Path is discarded.
|
||||
// The inside-ness of a pixel is determines by the non-zero winding rule,
|
||||
// similar to the SVG rule of the same name.
|
||||
//
|
||||
// Path generates no garbage and can be used for dynamic paths; path
|
||||
// data is stored directly in the Ops list supplied to Begin.
|
||||
type Path struct {
|
||||
ops *ops.Ops
|
||||
contour int
|
||||
pen f32.Point
|
||||
macro op.MacroOp
|
||||
start f32.Point
|
||||
hasSegments bool
|
||||
bounds f32internal.Rectangle
|
||||
hash maphash.Hash
|
||||
}
|
||||
|
||||
// Pos returns the current pen position.
|
||||
func (p *Path) Pos() f32.Point { return p.pen }
|
||||
|
||||
// Begin the path, storing the path data and final Op into ops.
|
||||
//
|
||||
// Caller must also call End to finish the drawing.
|
||||
// Forgetting to call it will result in a "panic: cannot mix multi ops with single ones".
|
||||
func (p *Path) Begin(o *op.Ops) {
|
||||
*p = Path{
|
||||
ops: &o.Internal,
|
||||
macro: op.Record(o),
|
||||
contour: 1,
|
||||
}
|
||||
p.hash.SetSeed(pathSeed)
|
||||
ops.BeginMulti(p.ops)
|
||||
data := ops.WriteMulti(p.ops, ops.TypeAuxLen)
|
||||
data[0] = byte(ops.TypeAux)
|
||||
}
|
||||
|
||||
// End returns a PathSpec ready to use in clipping operations.
|
||||
func (p *Path) End() PathSpec {
|
||||
p.gap()
|
||||
c := p.macro.Stop()
|
||||
ops.EndMulti(p.ops)
|
||||
return PathSpec{
|
||||
spec: c,
|
||||
hasSegments: p.hasSegments,
|
||||
bounds: p.bounds.Round(),
|
||||
hash: p.hash.Sum64(),
|
||||
}
|
||||
}
|
||||
|
||||
// Move moves the pen by the amount specified by delta.
|
||||
func (p *Path) Move(delta f32.Point) {
|
||||
to := delta.Add(p.pen)
|
||||
p.MoveTo(to)
|
||||
}
|
||||
|
||||
// MoveTo moves the pen to the specified absolute coordinate.
|
||||
func (p *Path) MoveTo(to f32.Point) {
|
||||
if p.pen == to {
|
||||
return
|
||||
}
|
||||
p.gap()
|
||||
p.end()
|
||||
p.pen = to
|
||||
p.start = to
|
||||
}
|
||||
|
||||
func (p *Path) gap() {
|
||||
if p.pen != p.start {
|
||||
// A closed contour starts and ends in the same point.
|
||||
// This move creates a gap in the contour, register it.
|
||||
data := ops.WriteMulti(p.ops, scene.CommandSize+4)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[0:], uint32(p.contour))
|
||||
p.cmd(data[4:], scene.Gap(p.pen, p.start))
|
||||
}
|
||||
}
|
||||
|
||||
// end completes the current contour.
|
||||
func (p *Path) end() {
|
||||
p.contour++
|
||||
}
|
||||
|
||||
// Line moves the pen by the amount specified by delta, recording a line.
|
||||
func (p *Path) Line(delta f32.Point) {
|
||||
to := delta.Add(p.pen)
|
||||
p.LineTo(to)
|
||||
}
|
||||
|
||||
// LineTo moves the pen to the absolute point specified, recording a line.
|
||||
func (p *Path) LineTo(to f32.Point) {
|
||||
if to == p.pen {
|
||||
return
|
||||
}
|
||||
data := ops.WriteMulti(p.ops, scene.CommandSize+4)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[0:], uint32(p.contour))
|
||||
p.cmd(data[4:], scene.Line(p.pen, to))
|
||||
p.expand(p.pen)
|
||||
p.expand(to)
|
||||
p.pen = to
|
||||
}
|
||||
|
||||
func (p *Path) cmd(data []byte, c scene.Command) {
|
||||
ops.EncodeCommand(data, c)
|
||||
p.hash.Write(data)
|
||||
}
|
||||
|
||||
func (p *Path) expand(pt f32.Point) {
|
||||
if !p.hasSegments {
|
||||
p.hasSegments = true
|
||||
p.bounds = f32internal.Rectangle{Min: pt, Max: pt}
|
||||
} else {
|
||||
b := p.bounds
|
||||
if pt.X < b.Min.X {
|
||||
b.Min.X = pt.X
|
||||
}
|
||||
if pt.Y < b.Min.Y {
|
||||
b.Min.Y = pt.Y
|
||||
}
|
||||
if pt.X > b.Max.X {
|
||||
b.Max.X = pt.X
|
||||
}
|
||||
if pt.Y > b.Max.Y {
|
||||
b.Max.Y = pt.Y
|
||||
}
|
||||
p.bounds = b
|
||||
}
|
||||
}
|
||||
|
||||
// Quad records a quadratic Bézier from the pen to end
|
||||
// with the control point ctrl.
|
||||
func (p *Path) Quad(ctrl, to f32.Point) {
|
||||
ctrl = ctrl.Add(p.pen)
|
||||
to = to.Add(p.pen)
|
||||
p.QuadTo(ctrl, to)
|
||||
}
|
||||
|
||||
// QuadTo records a quadratic Bézier from the pen to end
|
||||
// with the control point ctrl, with absolute coordinates.
|
||||
func (p *Path) QuadTo(ctrl, to f32.Point) {
|
||||
if ctrl == p.pen && to == p.pen {
|
||||
return
|
||||
}
|
||||
data := ops.WriteMulti(p.ops, scene.CommandSize+4)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[0:], uint32(p.contour))
|
||||
p.cmd(data[4:], scene.Quad(p.pen, ctrl, to))
|
||||
p.expand(p.pen)
|
||||
p.expand(ctrl)
|
||||
p.expand(to)
|
||||
p.pen = to
|
||||
}
|
||||
|
||||
// ArcTo adds an elliptical arc to the path. The implied ellipse is defined
|
||||
// by its focus points f1 and f2.
|
||||
// The arc starts in the current point and ends angle radians along the ellipse boundary.
|
||||
// The sign of angle determines the direction; positive being counter-clockwise,
|
||||
// negative clockwise.
|
||||
func (p *Path) ArcTo(f1, f2 f32.Point, angle float32) {
|
||||
m, segments := stroke.ArcTransform(p.pen, f1, f2, angle)
|
||||
for range segments {
|
||||
p0 := p.pen
|
||||
p1 := m.Transform(p0)
|
||||
p2 := m.Transform(p1)
|
||||
ctl := p1.Mul(2).Sub(p0.Add(p2).Mul(.5))
|
||||
p.QuadTo(ctl, p2)
|
||||
}
|
||||
}
|
||||
|
||||
// Arc is like ArcTo where f1 and f2 are relative to the current position.
|
||||
func (p *Path) Arc(f1, f2 f32.Point, angle float32) {
|
||||
f1 = f1.Add(p.pen)
|
||||
f2 = f2.Add(p.pen)
|
||||
p.ArcTo(f1, f2, angle)
|
||||
}
|
||||
|
||||
// Cube records a cubic Bézier from the pen through
|
||||
// two control points ending in to.
|
||||
func (p *Path) Cube(ctrl0, ctrl1, to f32.Point) {
|
||||
p.CubeTo(p.pen.Add(ctrl0), p.pen.Add(ctrl1), p.pen.Add(to))
|
||||
}
|
||||
|
||||
// CubeTo records a cubic Bézier from the pen through
|
||||
// two control points ending in to, with absolute coordinates.
|
||||
func (p *Path) CubeTo(ctrl0, ctrl1, to f32.Point) {
|
||||
if ctrl0 == p.pen && ctrl1 == p.pen && to == p.pen {
|
||||
return
|
||||
}
|
||||
data := ops.WriteMulti(p.ops, scene.CommandSize+4)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[0:], uint32(p.contour))
|
||||
p.cmd(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to))
|
||||
p.expand(p.pen)
|
||||
p.expand(ctrl0)
|
||||
p.expand(ctrl1)
|
||||
p.expand(to)
|
||||
p.pen = to
|
||||
}
|
||||
|
||||
// Close closes the path contour.
|
||||
func (p *Path) Close() {
|
||||
if p.pen != p.start {
|
||||
p.LineTo(p.start)
|
||||
}
|
||||
p.end()
|
||||
}
|
||||
|
||||
// Stroke represents a stroked path.
|
||||
type Stroke struct {
|
||||
Path PathSpec
|
||||
// Width of the stroked path.
|
||||
Width float32
|
||||
}
|
||||
|
||||
// Op returns a clip operation representing the stroke.
|
||||
func (s Stroke) Op() Op {
|
||||
return Op{
|
||||
path: s.Path,
|
||||
width: s.Width,
|
||||
}
|
||||
}
|
||||
|
||||
// Outline represents the area inside of a path, according to the
|
||||
// non-zero winding rule.
|
||||
type Outline struct {
|
||||
Path PathSpec
|
||||
}
|
||||
|
||||
// Op returns a clip operation representing the outline.
|
||||
func (o Outline) Op() Op {
|
||||
return Op{
|
||||
path: o.Path,
|
||||
outline: true,
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package clip provides operations for defining areas that applies to operations
|
||||
such as paints and pointer handlers.
|
||||
|
||||
The current clip is initially the infinite set. Pushing an Op sets the clip
|
||||
to the intersection of the current clip and pushed clip area. Popping the
|
||||
area restores the clip to its state before pushing.
|
||||
|
||||
General clipping areas are constructed with Path. Common cases such as
|
||||
rectangular clip areas also exist as convenient constructors.
|
||||
*/
|
||||
package clip
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package clip
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"gioui.org/f32"
|
||||
f32internal "gioui.org/internal/f32"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Rect represents the clip area of a pixel-aligned rectangle.
|
||||
type Rect image.Rectangle
|
||||
|
||||
// Op returns the op for the rectangle.
|
||||
func (r Rect) Op() Op {
|
||||
return Op{
|
||||
outline: true,
|
||||
path: r.Path(),
|
||||
}
|
||||
}
|
||||
|
||||
// Push the clip operation on the clip stack.
|
||||
func (r Rect) Push(ops *op.Ops) Stack {
|
||||
return r.Op().Push(ops)
|
||||
}
|
||||
|
||||
// Path returns the PathSpec for the rectangle.
|
||||
func (r Rect) Path() PathSpec {
|
||||
return PathSpec{
|
||||
shape: ops.Rect,
|
||||
bounds: image.Rectangle(r),
|
||||
}
|
||||
}
|
||||
|
||||
// UniformRRect returns an RRect with all corner radii set to the
|
||||
// provided radius.
|
||||
func UniformRRect(rect image.Rectangle, radius int) RRect {
|
||||
return RRect{
|
||||
Rect: rect,
|
||||
SE: radius,
|
||||
SW: radius,
|
||||
NE: radius,
|
||||
NW: radius,
|
||||
}
|
||||
}
|
||||
|
||||
// RRect represents the clip area of a rectangle with rounded
|
||||
// corners.
|
||||
//
|
||||
// Specify a square with corner radii equal to half the square size to
|
||||
// construct a circular clip area.
|
||||
type RRect struct {
|
||||
Rect image.Rectangle
|
||||
// The corner radii.
|
||||
SE, SW, NW, NE int
|
||||
}
|
||||
|
||||
// Op returns the op for the rounded rectangle.
|
||||
func (rr RRect) Op(ops *op.Ops) Op {
|
||||
if rr.SE == 0 && rr.SW == 0 && rr.NW == 0 && rr.NE == 0 {
|
||||
return Rect(rr.Rect).Op()
|
||||
}
|
||||
return Outline{Path: rr.Path(ops)}.Op()
|
||||
}
|
||||
|
||||
// Push the rectangle clip on the clip stack.
|
||||
func (rr RRect) Push(ops *op.Ops) Stack {
|
||||
return rr.Op(ops).Push(ops)
|
||||
}
|
||||
|
||||
// Path returns the PathSpec for the rounded rectangle.
|
||||
func (rr RRect) Path(ops *op.Ops) PathSpec {
|
||||
var p Path
|
||||
p.Begin(ops)
|
||||
|
||||
// https://pomax.github.io/bezierinfo/#circles_cubic.
|
||||
const q = 4 * (math.Sqrt2 - 1) / 3
|
||||
const iq = 1 - q
|
||||
|
||||
se, sw, nw, ne := float32(rr.SE), float32(rr.SW), float32(rr.NW), float32(rr.NE)
|
||||
rrf := f32internal.FRect(rr.Rect)
|
||||
w, n, e, s := rrf.Min.X, rrf.Min.Y, rrf.Max.X, rrf.Max.Y
|
||||
|
||||
p.MoveTo(f32.Point{X: w + nw, Y: n})
|
||||
p.LineTo(f32.Point{X: e - ne, Y: n}) // N
|
||||
p.CubeTo( // NE
|
||||
f32.Point{X: e - ne*iq, Y: n},
|
||||
f32.Point{X: e, Y: n + ne*iq},
|
||||
f32.Point{X: e, Y: n + ne})
|
||||
p.LineTo(f32.Point{X: e, Y: s - se}) // E
|
||||
p.CubeTo( // SE
|
||||
f32.Point{X: e, Y: s - se*iq},
|
||||
f32.Point{X: e - se*iq, Y: s},
|
||||
f32.Point{X: e - se, Y: s})
|
||||
p.LineTo(f32.Point{X: w + sw, Y: s}) // S
|
||||
p.CubeTo( // SW
|
||||
f32.Point{X: w + sw*iq, Y: s},
|
||||
f32.Point{X: w, Y: s - sw*iq},
|
||||
f32.Point{X: w, Y: s - sw})
|
||||
p.LineTo(f32.Point{X: w, Y: n + nw}) // W
|
||||
p.CubeTo( // NW
|
||||
f32.Point{X: w, Y: n + nw*iq},
|
||||
f32.Point{X: w + nw*iq, Y: n},
|
||||
f32.Point{X: w + nw, Y: n})
|
||||
|
||||
return p.End()
|
||||
}
|
||||
|
||||
// Ellipse represents the largest axis-aligned ellipse that
|
||||
// is contained in its bounds.
|
||||
type Ellipse image.Rectangle
|
||||
|
||||
// Op returns the op for the filled ellipse.
|
||||
func (e Ellipse) Op(ops *op.Ops) Op {
|
||||
return Outline{Path: e.Path(ops)}.Op()
|
||||
}
|
||||
|
||||
// Push the filled ellipse clip op on the clip stack.
|
||||
func (e Ellipse) Push(ops *op.Ops) Stack {
|
||||
return e.Op(ops).Push(ops)
|
||||
}
|
||||
|
||||
// Path constructs a path for the ellipse.
|
||||
func (e Ellipse) Path(o *op.Ops) PathSpec {
|
||||
bounds := image.Rectangle(e)
|
||||
if bounds.Dx() == 0 || bounds.Dy() == 0 {
|
||||
return PathSpec{shape: ops.Rect}
|
||||
}
|
||||
|
||||
var p Path
|
||||
p.Begin(o)
|
||||
|
||||
bf := f32internal.FRect(bounds)
|
||||
center := bf.Max.Add(bf.Min).Mul(.5)
|
||||
diam := bf.Dx()
|
||||
r := diam * .5
|
||||
// We'll model the ellipse as a circle scaled in the Y
|
||||
// direction.
|
||||
scale := bf.Dy() / diam
|
||||
|
||||
// https://pomax.github.io/bezierinfo/#circles_cubic.
|
||||
const q = 4 * (math.Sqrt2 - 1) / 3
|
||||
|
||||
curve := r * q
|
||||
top := f32.Point{X: center.X, Y: center.Y - r*scale}
|
||||
|
||||
p.MoveTo(top)
|
||||
p.CubeTo(
|
||||
f32.Point{X: center.X + curve, Y: center.Y - r*scale},
|
||||
f32.Point{X: center.X + r, Y: center.Y - curve*scale},
|
||||
f32.Point{X: center.X + r, Y: center.Y},
|
||||
)
|
||||
p.CubeTo(
|
||||
f32.Point{X: center.X + r, Y: center.Y + curve*scale},
|
||||
f32.Point{X: center.X + curve, Y: center.Y + r*scale},
|
||||
f32.Point{X: center.X, Y: center.Y + r*scale},
|
||||
)
|
||||
p.CubeTo(
|
||||
f32.Point{X: center.X - curve, Y: center.Y + r*scale},
|
||||
f32.Point{X: center.X - r, Y: center.Y + curve*scale},
|
||||
f32.Point{X: center.X - r, Y: center.Y},
|
||||
)
|
||||
p.CubeTo(
|
||||
f32.Point{X: center.X - r, Y: center.Y - curve*scale},
|
||||
f32.Point{X: center.X - curve, Y: center.Y - r*scale},
|
||||
top,
|
||||
)
|
||||
ellipse := p.End()
|
||||
ellipse.shape = ops.Ellipse
|
||||
return ellipse
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package op implements operations for updating a user interface.
|
||||
|
||||
Gio programs use operations, or ops, for describing their user
|
||||
interfaces. There are operations for drawing, defining input
|
||||
handlers, changing window properties as well as operations for
|
||||
controlling the execution of other operations.
|
||||
|
||||
Ops represents a list of operations. The most important use
|
||||
for an Ops list is to describe a complete user interface update
|
||||
to a ui/app.Window's Update method.
|
||||
|
||||
Drawing a colored square:
|
||||
|
||||
import "gioui.org/unit"
|
||||
import "gioui.org/app"
|
||||
import "gioui.org/op/paint"
|
||||
|
||||
var w app.Window
|
||||
var e system.FrameEvent
|
||||
ops := new(op.Ops)
|
||||
...
|
||||
ops.Reset()
|
||||
paint.ColorOp{Color: ...}.Add(ops)
|
||||
paint.PaintOp{Rect: ...}.Add(ops)
|
||||
e.Frame(ops)
|
||||
|
||||
# State
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
// Set the color.
|
||||
paint.ColorOp{...}.Add(ops)
|
||||
// Apply an offset to subsequent operations.
|
||||
stack := op.Offset(...).Push(ops)
|
||||
...
|
||||
// Undo the offset transformation.
|
||||
stack.Pop()
|
||||
|
||||
The MacroOp records a list of operations to be executed later:
|
||||
|
||||
ops := new(op.Ops)
|
||||
macro := op.Record(ops)
|
||||
// Record operations by adding them.
|
||||
...
|
||||
// End recording.
|
||||
call := macro.Stop()
|
||||
|
||||
// replay the recorded operations:
|
||||
call.Add(ops)
|
||||
*/
|
||||
package op
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/ops"
|
||||
)
|
||||
|
||||
// Ops holds a list of operations. Operations are stored in
|
||||
// serialized form to avoid garbage during construction of
|
||||
// the ops list.
|
||||
type Ops struct {
|
||||
// Internal is for internal use, despite being exported.
|
||||
Internal ops.Ops
|
||||
}
|
||||
|
||||
// MacroOp records a list of operations for later use.
|
||||
type MacroOp struct {
|
||||
ops *ops.Ops
|
||||
id ops.StackID
|
||||
pc ops.PC
|
||||
}
|
||||
|
||||
// CallOp invokes the operations recorded by Record.
|
||||
type CallOp struct {
|
||||
// Ops is the list of operations to invoke.
|
||||
ops *ops.Ops
|
||||
start ops.PC
|
||||
end ops.PC
|
||||
}
|
||||
|
||||
// InvalidateCmd requests a redraw at the given time. Use
|
||||
// the zero value to request an immediate redraw.
|
||||
type InvalidateCmd struct {
|
||||
At time.Time
|
||||
}
|
||||
|
||||
// TransformOp represents a transformation that can be pushed on the
|
||||
// transformation stack.
|
||||
type TransformOp struct {
|
||||
t f32.Affine2D
|
||||
}
|
||||
|
||||
// TransformStack represents a TransformOp pushed on the transformation stack.
|
||||
type TransformStack struct {
|
||||
id ops.StackID
|
||||
macroID uint32
|
||||
ops *ops.Ops
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Defer(o *Ops, c CallOp) {
|
||||
if c.ops == nil {
|
||||
return
|
||||
}
|
||||
state := ops.Save(&o.Internal)
|
||||
// Wrap c in a macro that loads the saved state before execution.
|
||||
m := Record(o)
|
||||
state.Load()
|
||||
c.Add(o)
|
||||
c = m.Stop()
|
||||
// A Defer is recorded as a TypeDefer followed by the
|
||||
// wrapped macro.
|
||||
data := ops.Write(&o.Internal, ops.TypeDeferLen)
|
||||
data[0] = byte(ops.TypeDefer)
|
||||
c.Add(o)
|
||||
}
|
||||
|
||||
// Reset the Ops, preparing it for re-use. Reset invalidates
|
||||
// any recorded macros.
|
||||
func (o *Ops) Reset() {
|
||||
ops.Reset(&o.Internal)
|
||||
}
|
||||
|
||||
// Record a macro of operations.
|
||||
func Record(o *Ops) MacroOp {
|
||||
m := MacroOp{
|
||||
ops: &o.Internal,
|
||||
id: ops.PushMacro(&o.Internal),
|
||||
pc: ops.PCFor(&o.Internal),
|
||||
}
|
||||
// Reserve room for a macro definition. Updated in Stop.
|
||||
data := ops.Write(m.ops, ops.TypeMacroLen)
|
||||
data[0] = byte(ops.TypeMacro)
|
||||
return m
|
||||
}
|
||||
|
||||
// Stop ends a previously started recording and returns an
|
||||
// operation for replaying it.
|
||||
func (m MacroOp) Stop() CallOp {
|
||||
ops.PopMacro(m.ops, m.id)
|
||||
ops.FillMacro(m.ops, m.pc)
|
||||
return CallOp{
|
||||
ops: m.ops,
|
||||
// Skip macro header.
|
||||
start: m.pc.Add(ops.TypeMacro),
|
||||
end: ops.PCFor(m.ops),
|
||||
}
|
||||
}
|
||||
|
||||
// Add the recorded list of operations. Add
|
||||
// panics if the Ops containing the recording
|
||||
// has been reset.
|
||||
func (c CallOp) Add(o *Ops) {
|
||||
if c.ops == nil {
|
||||
return
|
||||
}
|
||||
ops.AddCall(&o.Internal, c.ops, c.start, c.end)
|
||||
}
|
||||
|
||||
// Offset converts an offset to a TransformOp.
|
||||
func Offset(off image.Point) TransformOp {
|
||||
offf := f32.Pt(float32(off.X), float32(off.Y))
|
||||
return Affine(f32.AffineId().Offset(offf))
|
||||
}
|
||||
|
||||
// Affine creates a TransformOp representing the transformation a.
|
||||
func Affine(a f32.Affine2D) TransformOp {
|
||||
return TransformOp{t: a}
|
||||
}
|
||||
|
||||
// Push the current transformation to the stack and then multiply the
|
||||
// current transformation with t.
|
||||
func (t TransformOp) Push(o *Ops) TransformStack {
|
||||
id, macroID := ops.PushOp(&o.Internal, ops.TransStack)
|
||||
t.add(o, true)
|
||||
return TransformStack{ops: &o.Internal, id: id, macroID: macroID}
|
||||
}
|
||||
|
||||
// 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 := ops.Write(&o.Internal, ops.TypeTransformLen)
|
||||
data[0] = byte(ops.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() {
|
||||
ops.PopOp(t.ops, ops.TransStack, t.id, t.macroID)
|
||||
data := ops.Write(t.ops, ops.TypePopTransformLen)
|
||||
data[0] = byte(ops.TypePopTransform)
|
||||
}
|
||||
|
||||
func (InvalidateCmd) ImplementsCommand() {}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package paint provides drawing operations for 2D graphics.
|
||||
|
||||
The PaintOp operation fills the current clip with the current brush, taking the
|
||||
current transformation into account. Drawing outside the current clip area is
|
||||
ignored.
|
||||
|
||||
The current brush is set by either a ColorOp for a constant color, or
|
||||
ImageOp for an image, or LinearGradientOp for gradients.
|
||||
|
||||
All color.NRGBA values are in the sRGB color space.
|
||||
*/
|
||||
package paint
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package paint
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"math"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
)
|
||||
|
||||
// ImageFilter is the scaling filter for images.
|
||||
type ImageFilter byte
|
||||
|
||||
const (
|
||||
// FilterLinear uses linear interpolation for scaling.
|
||||
FilterLinear ImageFilter = iota
|
||||
// FilterNearest uses nearest neighbor interpolation for scaling.
|
||||
FilterNearest
|
||||
)
|
||||
|
||||
// ImageOp sets the brush to an image.
|
||||
type ImageOp struct {
|
||||
Filter ImageFilter
|
||||
|
||||
uniform bool
|
||||
color color.NRGBA
|
||||
src *image.RGBA
|
||||
|
||||
// handle is a key to uniquely identify this ImageOp
|
||||
// in a map of cached textures.
|
||||
handle any
|
||||
}
|
||||
|
||||
// ColorOp sets the brush to a constant color.
|
||||
type ColorOp struct {
|
||||
Color color.NRGBA
|
||||
}
|
||||
|
||||
// LinearGradientOp sets the brush to a gradient starting at stop1 with color1 and
|
||||
// ending at stop2 with color2.
|
||||
type LinearGradientOp struct {
|
||||
Stop1 f32.Point
|
||||
Color1 color.NRGBA
|
||||
Stop2 f32.Point
|
||||
Color2 color.NRGBA
|
||||
}
|
||||
|
||||
// PaintOp fills the current clip area with the current brush.
|
||||
type PaintOp struct{}
|
||||
|
||||
// OpacityStack represents an opacity applied to all painting operations
|
||||
// until Pop is called.
|
||||
type OpacityStack struct {
|
||||
id ops.StackID
|
||||
macroID uint32
|
||||
ops *ops.Ops
|
||||
}
|
||||
|
||||
// NewImageOp creates an ImageOp backed by src.
|
||||
//
|
||||
// NewImageOp assumes the backing image is immutable, and may cache a
|
||||
// copy of its contents in a GPU-friendly way. Create new ImageOps to
|
||||
// ensure that changes to an image is reflected in the display of
|
||||
// it.
|
||||
func NewImageOp(src image.Image) ImageOp {
|
||||
switch src := src.(type) {
|
||||
case *image.Uniform:
|
||||
col := color.NRGBAModel.Convert(src.C).(color.NRGBA)
|
||||
return ImageOp{
|
||||
uniform: true,
|
||||
color: col,
|
||||
}
|
||||
case *image.RGBA:
|
||||
return ImageOp{
|
||||
src: src,
|
||||
handle: new(int),
|
||||
}
|
||||
}
|
||||
|
||||
sz := src.Bounds().Size()
|
||||
// Copy the image into a GPU friendly format.
|
||||
dst := image.NewRGBA(image.Rectangle{
|
||||
Max: sz,
|
||||
})
|
||||
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
|
||||
return ImageOp{
|
||||
src: dst,
|
||||
handle: new(int),
|
||||
}
|
||||
}
|
||||
|
||||
func (i ImageOp) Size() image.Point {
|
||||
if i.src == nil {
|
||||
return image.Point{}
|
||||
}
|
||||
return i.src.Bounds().Size()
|
||||
}
|
||||
|
||||
func (i ImageOp) Add(o *op.Ops) {
|
||||
if i.uniform {
|
||||
ColorOp{
|
||||
Color: i.color,
|
||||
}.Add(o)
|
||||
return
|
||||
} else if i.src == nil || i.src.Bounds().Empty() {
|
||||
return
|
||||
}
|
||||
data := ops.Write2(&o.Internal, ops.TypeImageLen, i.src, i.handle)
|
||||
data[0] = byte(ops.TypeImage)
|
||||
data[1] = byte(i.Filter)
|
||||
}
|
||||
|
||||
func (c ColorOp) Add(o *op.Ops) {
|
||||
data := ops.Write(&o.Internal, ops.TypeColorLen)
|
||||
data[0] = byte(ops.TypeColor)
|
||||
data[1] = c.Color.R
|
||||
data[2] = c.Color.G
|
||||
data[3] = c.Color.B
|
||||
data[4] = c.Color.A
|
||||
}
|
||||
|
||||
func (c LinearGradientOp) Add(o *op.Ops) {
|
||||
data := ops.Write(&o.Internal, ops.TypeLinearGradientLen)
|
||||
data[0] = byte(ops.TypeLinearGradient)
|
||||
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[1:], math.Float32bits(c.Stop1.X))
|
||||
bo.PutUint32(data[5:], math.Float32bits(c.Stop1.Y))
|
||||
bo.PutUint32(data[9:], math.Float32bits(c.Stop2.X))
|
||||
bo.PutUint32(data[13:], math.Float32bits(c.Stop2.Y))
|
||||
|
||||
data[17+0] = c.Color1.R
|
||||
data[17+1] = c.Color1.G
|
||||
data[17+2] = c.Color1.B
|
||||
data[17+3] = c.Color1.A
|
||||
data[21+0] = c.Color2.R
|
||||
data[21+1] = c.Color2.G
|
||||
data[21+2] = c.Color2.B
|
||||
data[21+3] = c.Color2.A
|
||||
}
|
||||
|
||||
func (d PaintOp) Add(o *op.Ops) {
|
||||
data := ops.Write(&o.Internal, ops.TypePaintLen)
|
||||
data[0] = byte(ops.TypePaint)
|
||||
}
|
||||
|
||||
// FillShape fills the clip shape with a color.
|
||||
func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) {
|
||||
defer shape.Push(ops).Pop()
|
||||
Fill(ops, c)
|
||||
}
|
||||
|
||||
// Fill paints an infinitely large plane with the provided color. It
|
||||
// is intended to be used with a clip.Op already in place to limit
|
||||
// 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) {
|
||||
ColorOp{Color: c}.Add(ops)
|
||||
PaintOp{}.Add(ops)
|
||||
}
|
||||
|
||||
// PushOpacity creates a drawing layer with an opacity in the range [0;1].
|
||||
// The layer includes every subsequent drawing operation until [OpacityStack.Pop]
|
||||
// is called.
|
||||
//
|
||||
// The layer is drawn in two steps. First, the layer operations are
|
||||
// drawn to a separate image. Then, the image is blended on top of
|
||||
// the frame, with the opacity used as the blending factor.
|
||||
func PushOpacity(o *op.Ops, opacity float32) OpacityStack {
|
||||
if opacity > 1 {
|
||||
opacity = 1
|
||||
}
|
||||
if opacity < 0 {
|
||||
opacity = 0
|
||||
}
|
||||
id, macroID := ops.PushOp(&o.Internal, ops.OpacityStack)
|
||||
data := ops.Write(&o.Internal, ops.TypePushOpacityLen)
|
||||
bo := binary.LittleEndian
|
||||
data[0] = byte(ops.TypePushOpacity)
|
||||
bo.PutUint32(data[1:], math.Float32bits(opacity))
|
||||
return OpacityStack{ops: &o.Internal, id: id, macroID: macroID}
|
||||
}
|
||||
|
||||
func (t OpacityStack) Pop() {
|
||||
ops.PopOp(t.ops, ops.OpacityStack, t.id, t.macroID)
|
||||
data := ops.Write(t.ops, ops.TypePopOpacityLen)
|
||||
data[0] = byte(ops.TypePopOpacity)
|
||||
}
|
||||
Reference in New Issue
Block a user