github.com/utopiagio/gio@v0.0.8/op/op.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  /*
     4  Package op implements operations for updating a user interface.
     5  
     6  Gio programs use operations, or ops, for describing their user
     7  interfaces. There are operations for drawing, defining input
     8  handlers, changing window properties as well as operations for
     9  controlling the execution of other operations.
    10  
    11  Ops represents a list of operations. The most important use
    12  for an Ops list is to describe a complete user interface update
    13  to a ui/app.Window's Update method.
    14  
    15  Drawing a colored square:
    16  
    17  	import "github.com/utopiagio/gio/unit"
    18  	import "github.com/utopiagio/gio/app"
    19  	import "github.com/utopiagio/gio/op/paint"
    20  
    21  	var w app.Window
    22  	var e system.FrameEvent
    23  	ops := new(op.Ops)
    24  	...
    25  	ops.Reset()
    26  	paint.ColorOp{Color: ...}.Add(ops)
    27  	paint.PaintOp{Rect: ...}.Add(ops)
    28  	e.Frame(ops)
    29  
    30  # State
    31  
    32  An Ops list can be viewed as a very simple virtual machine: it has state such
    33  as transformation and color and execution flow can be controlled with macros.
    34  
    35  Some state, such as the current color, is modified directly by operations with
    36  Add methods. Other state, such as transformation and clip shape, are
    37  represented by stacks.
    38  
    39  This example sets the simple color state and pushes an offset to the
    40  transformation stack.
    41  
    42  	ops := new(op.Ops)
    43  	// Set the color.
    44  	paint.ColorOp{...}.Add(ops)
    45  	// Apply an offset to subsequent operations.
    46  	stack := op.Offset(...).Push(ops)
    47  	...
    48  	// Undo the offset transformation.
    49  	stack.Pop()
    50  
    51  The MacroOp records a list of operations to be executed later:
    52  
    53  	ops := new(op.Ops)
    54  	macro := op.Record(ops)
    55  	// Record operations by adding them.
    56  	...
    57  	// End recording.
    58  	call := macro.Stop()
    59  
    60  	// replay the recorded operations:
    61  	call.Add(ops)
    62  */
    63  package op
    64  
    65  import (
    66  	"encoding/binary"
    67  	"image"
    68  	"math"
    69  	"time"
    70  
    71  	"github.com/utopiagio/gio/f32"
    72  	"github.com/utopiagio/gio/internal/ops"
    73  )
    74  
    75  // Ops holds a list of operations. Operations are stored in
    76  // serialized form to avoid garbage during construction of
    77  // the ops list.
    78  type Ops struct {
    79  	// Internal is for internal use, despite being exported.
    80  	Internal ops.Ops
    81  }
    82  
    83  // MacroOp records a list of operations for later use.
    84  type MacroOp struct {
    85  	ops *ops.Ops
    86  	id  ops.StackID
    87  	pc  ops.PC
    88  }
    89  
    90  // CallOp invokes the operations recorded by Record.
    91  type CallOp struct {
    92  	// Ops is the list of operations to invoke.
    93  	ops   *ops.Ops
    94  	start ops.PC
    95  	end   ops.PC
    96  }
    97  
    98  // InvalidateCmd requests a redraw at the given time. Use
    99  // the zero value to request an immediate redraw.
   100  type InvalidateCmd struct {
   101  	At time.Time
   102  }
   103  
   104  // TransformOp represents a transformation that can be pushed on the
   105  // transformation stack.
   106  type TransformOp struct {
   107  	t f32.Affine2D
   108  }
   109  
   110  // TransformStack represents a TransformOp pushed on the transformation stack.
   111  type TransformStack struct {
   112  	id      ops.StackID
   113  	macroID uint32
   114  	ops     *ops.Ops
   115  }
   116  
   117  // Defer executes c after all other operations have completed, including
   118  // previously deferred operations.
   119  // Defer saves the transformation stack and pushes it prior to executing
   120  // c. All other operation state is reset.
   121  //
   122  // Note that deferred operations are executed in first-in-first-out order,
   123  // unlike the Go facility of the same name.
   124  func Defer(o *Ops, c CallOp) {
   125  	if c.ops == nil {
   126  		return
   127  	}
   128  	state := ops.Save(&o.Internal)
   129  	// Wrap c in a macro that loads the saved state before execution.
   130  	m := Record(o)
   131  	state.Load()
   132  	c.Add(o)
   133  	c = m.Stop()
   134  	// A Defer is recorded as a TypeDefer followed by the
   135  	// wrapped macro.
   136  	data := ops.Write(&o.Internal, ops.TypeDeferLen)
   137  	data[0] = byte(ops.TypeDefer)
   138  	c.Add(o)
   139  }
   140  
   141  // Reset the Ops, preparing it for re-use. Reset invalidates
   142  // any recorded macros.
   143  func (o *Ops) Reset() {
   144  	ops.Reset(&o.Internal)
   145  }
   146  
   147  // Record a macro of operations.
   148  func Record(o *Ops) MacroOp {
   149  	m := MacroOp{
   150  		ops: &o.Internal,
   151  		id:  ops.PushMacro(&o.Internal),
   152  		pc:  ops.PCFor(&o.Internal),
   153  	}
   154  	// Reserve room for a macro definition. Updated in Stop.
   155  	data := ops.Write(m.ops, ops.TypeMacroLen)
   156  	data[0] = byte(ops.TypeMacro)
   157  	return m
   158  }
   159  
   160  // Stop ends a previously started recording and returns an
   161  // operation for replaying it.
   162  func (m MacroOp) Stop() CallOp {
   163  	ops.PopMacro(m.ops, m.id)
   164  	ops.FillMacro(m.ops, m.pc)
   165  	return CallOp{
   166  		ops: m.ops,
   167  		// Skip macro header.
   168  		start: m.pc.Add(ops.TypeMacro),
   169  		end:   ops.PCFor(m.ops),
   170  	}
   171  }
   172  
   173  // Add the recorded list of operations. Add
   174  // panics if the Ops containing the recording
   175  // has been reset.
   176  func (c CallOp) Add(o *Ops) {
   177  	if c.ops == nil {
   178  		return
   179  	}
   180  	ops.AddCall(&o.Internal, c.ops, c.start, c.end)
   181  }
   182  
   183  // Offset converts an offset to a TransformOp.
   184  func Offset(off image.Point) TransformOp {
   185  	offf := f32.Pt(float32(off.X), float32(off.Y))
   186  	return Affine(f32.Affine2D{}.Offset(offf))
   187  }
   188  
   189  // Affine creates a TransformOp representing the transformation a.
   190  func Affine(a f32.Affine2D) TransformOp {
   191  	return TransformOp{t: a}
   192  }
   193  
   194  // Push the current transformation to the stack and then multiply the
   195  // current transformation with t.
   196  func (t TransformOp) Push(o *Ops) TransformStack {
   197  	id, macroID := ops.PushOp(&o.Internal, ops.TransStack)
   198  	t.add(o, true)
   199  	return TransformStack{ops: &o.Internal, id: id, macroID: macroID}
   200  }
   201  
   202  // Add is like Push except it doesn't push the current transformation to the
   203  // stack.
   204  func (t TransformOp) Add(o *Ops) {
   205  	t.add(o, false)
   206  }
   207  
   208  func (t TransformOp) add(o *Ops, push bool) {
   209  	data := ops.Write(&o.Internal, ops.TypeTransformLen)
   210  	data[0] = byte(ops.TypeTransform)
   211  	if push {
   212  		data[1] = 1
   213  	}
   214  	bo := binary.LittleEndian
   215  	a, b, c, d, e, f := t.t.Elems()
   216  	bo.PutUint32(data[2:], math.Float32bits(a))
   217  	bo.PutUint32(data[2+4*1:], math.Float32bits(b))
   218  	bo.PutUint32(data[2+4*2:], math.Float32bits(c))
   219  	bo.PutUint32(data[2+4*3:], math.Float32bits(d))
   220  	bo.PutUint32(data[2+4*4:], math.Float32bits(e))
   221  	bo.PutUint32(data[2+4*5:], math.Float32bits(f))
   222  }
   223  
   224  func (t TransformStack) Pop() {
   225  	ops.PopOp(t.ops, ops.TransStack, t.id, t.macroID)
   226  	data := ops.Write(t.ops, ops.TypePopTransformLen)
   227  	data[0] = byte(ops.TypePopTransform)
   228  }
   229  
   230  func (InvalidateCmd) ImplementsCommand() {}