mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
391725b9d0
Merge package opconsts into ops as well; it only existed to break import cycles. Signed-off-by: Elias Naur <mail@eliasnaur.com>
288 lines
7.0 KiB
Go
288 lines
7.0 KiB
Go
// 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.
|
|
op.InvalidateOp{}.Add(ops)
|
|
...
|
|
// 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
|
|
pc ops.PC
|
|
}
|
|
|
|
// InvalidateOp requests a redraw at the given time. Use
|
|
// the zero value to request an immediate redraw.
|
|
type InvalidateOp 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 int
|
|
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 := o.Internal.Save()
|
|
// 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 := o.Internal.Write(ops.TypeDeferLen)
|
|
data[0] = byte(ops.TypeDefer)
|
|
c.Add(o)
|
|
}
|
|
|
|
type SaveStack struct {
|
|
ops *ops.Ops
|
|
clip struct {
|
|
id ops.StackID
|
|
macroID int
|
|
}
|
|
trans TransformStack
|
|
}
|
|
|
|
// Deprecated: use state-specific stack operations instead (TransformOp.Push
|
|
// etc.).
|
|
func Save(o *Ops) SaveStack {
|
|
st := SaveStack{
|
|
ops: &o.Internal,
|
|
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.Internal.PushOp(ops.ClipStack)
|
|
// Push clip stack with no-op (infinite) clipping rect. Copied from clip.Op.Push.
|
|
bo := binary.LittleEndian
|
|
data := o.Internal.Write(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))
|
|
data[17] = byte(1) // Outline
|
|
data[18] = byte(1) // Push
|
|
}
|
|
return st
|
|
}
|
|
|
|
func (s SaveStack) Load() {
|
|
// Pop clip.
|
|
s.ops.PopOp(ops.ClipStack, s.clip.id, s.clip.macroID)
|
|
data := s.ops.Write(ops.TypePopClipLen)
|
|
data[0] = byte(ops.TypePopClip)
|
|
|
|
s.trans.Pop()
|
|
}
|
|
|
|
// Reset the Ops, preparing it for re-use. Reset invalidates
|
|
// any recorded macros.
|
|
func (o *Ops) Reset() {
|
|
o.Internal.Reset()
|
|
}
|
|
|
|
// Record a macro of operations.
|
|
func Record(o *Ops) MacroOp {
|
|
m := MacroOp{
|
|
ops: &o.Internal,
|
|
id: o.Internal.PushMacro(),
|
|
pc: o.Internal.PC(),
|
|
}
|
|
// Reserve room for a macro definition. Updated in Stop.
|
|
m.ops.Write(ops.TypeMacroLen)
|
|
m.fill()
|
|
return m
|
|
}
|
|
|
|
// Stop ends a previously started recording and returns an
|
|
// operation for replaying it.
|
|
func (m MacroOp) Stop() CallOp {
|
|
m.ops.PopMacro(m.id)
|
|
m.fill()
|
|
return CallOp{
|
|
ops: m.ops,
|
|
pc: m.pc,
|
|
}
|
|
}
|
|
|
|
func (m MacroOp) fill() {
|
|
m.ops.FillMacro(m.pc)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
o.Internal.AddCall(c.ops, c.pc)
|
|
}
|
|
|
|
func (r InvalidateOp) Add(o *Ops) {
|
|
data := o.Internal.Write(ops.TypeRedrawLen)
|
|
data[0] = byte(ops.TypeInvalidate)
|
|
bo := binary.LittleEndian
|
|
// UnixNano cannot represent the zero time.
|
|
if t := r.At; !t.IsZero() {
|
|
nanos := t.UnixNano()
|
|
if nanos > 0 {
|
|
bo.PutUint64(data[1:], uint64(nanos))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Offset creates a TransformOp with the offset o.
|
|
func Offset(o f32.Point) TransformOp {
|
|
return TransformOp{t: f32.Affine2D{}.Offset(o)}
|
|
}
|
|
|
|
// 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 := o.Internal.PushOp(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 := o.Internal.Write(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() {
|
|
t.ops.PopOp(ops.TransStack, t.id, t.macroID)
|
|
data := t.ops.Write(ops.TypePopTransformLen)
|
|
data[0] = byte(ops.TypePopTransform)
|
|
}
|