github.com/Seikaijyu/gio@v0.0.1/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/Seikaijyu/gio/unit"
    18  	import "github.com/Seikaijyu/gio/app"
    19  	import "github.com/Seikaijyu/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  	op.InvalidateOp{}.Add(ops)
    57  	...
    58  	// End recording.
    59  	call := macro.Stop()
    60  
    61  	// replay the recorded operations:
    62  	call.Add(ops)
    63  */
    64  package op
    65  
    66  import (
    67  	"encoding/binary"
    68  	"image"
    69  	"math"
    70  	"time"
    71  
    72  	"github.com/Seikaijyu/gio/f32"
    73  	"github.com/Seikaijyu/gio/internal/ops"
    74  )
    75  
    76  // Ops holds a list of operations. Operations are stored in
    77  // serialized form to avoid garbage during construction of
    78  // the ops list.
    79  type Ops struct {
    80  	// Internal is for internal use, despite being exported.
    81  	Internal ops.Ops
    82  }
    83  
    84  // MacroOp records a list of operations for later use.
    85  type MacroOp struct {
    86  	ops *ops.Ops
    87  	id  ops.StackID
    88  	pc  ops.PC
    89  }
    90  
    91  // CallOp invokes the operations recorded by Record.
    92  type CallOp struct {
    93  	// Ops is the list of operations to invoke.
    94  	ops   *ops.Ops
    95  	start ops.PC
    96  	end   ops.PC
    97  }
    98  
    99  // InvalidateOp requests a redraw at the given time. Use
   100  // the zero value to request an immediate redraw.
   101  type InvalidateOp struct {
   102  	At time.Time
   103  }
   104  
   105  // TransformOp represents a transformation that can be pushed on the
   106  // transformation stack.
   107  type TransformOp struct {
   108  	t f32.Affine2D
   109  }
   110  
   111  // TransformStack represents a TransformOp pushed on the transformation stack.
   112  type TransformStack struct {
   113  	id      ops.StackID
   114  	macroID uint32
   115  	ops     *ops.Ops
   116  }
   117  
   118  // Defer executes c after all other operations have completed, including
   119  // previously deferred operations.
   120  // Defer saves the transformation stack and pushes it prior to executing
   121  // c. All other operation state is reset.
   122  //
   123  // Note that deferred operations are executed in first-in-first-out order,
   124  // unlike the Go facility of the same name.
   125  func Defer(o *Ops, c CallOp) {
   126  	if c.ops == nil {
   127  		return
   128  	}
   129  	state := ops.Save(&o.Internal)
   130  	// Wrap c in a macro that loads the saved state before execution.
   131  	m := Record(o)
   132  	state.Load()
   133  	c.Add(o)
   134  	c = m.Stop()
   135  	// A Defer is recorded as a TypeDefer followed by the
   136  	// wrapped macro.
   137  	data := ops.Write(&o.Internal, ops.TypeDeferLen)
   138  	data[0] = byte(ops.TypeDefer)
   139  	c.Add(o)
   140  }
   141  
   142  // Reset the Ops, preparing it for re-use. Reset invalidates
   143  // any recorded macros.
   144  func (o *Ops) Reset() {
   145  	ops.Reset(&o.Internal)
   146  }
   147  
   148  // Record a macro of operations.
   149  func Record(o *Ops) MacroOp {
   150  	m := MacroOp{
   151  		ops: &o.Internal,
   152  		id:  ops.PushMacro(&o.Internal),
   153  		pc:  ops.PCFor(&o.Internal),
   154  	}
   155  	// Reserve room for a macro definition. Updated in Stop.
   156  	data := ops.Write(m.ops, ops.TypeMacroLen)
   157  	data[0] = byte(ops.TypeMacro)
   158  	return m
   159  }
   160  
   161  // Stop ends a previously started recording and returns an
   162  // operation for replaying it.
   163  func (m MacroOp) Stop() CallOp {
   164  	ops.PopMacro(m.ops, m.id)
   165  	ops.FillMacro(m.ops, m.pc)
   166  	return CallOp{
   167  		ops: m.ops,
   168  		// Skip macro header.
   169  		start: m.pc.Add(ops.TypeMacro),
   170  		end:   ops.PCFor(m.ops),
   171  	}
   172  }
   173  
   174  // Add the recorded list of operations. Add
   175  // panics if the Ops containing the recording
   176  // has been reset.
   177  func (c CallOp) Add(o *Ops) {
   178  	if c.ops == nil {
   179  		return
   180  	}
   181  	ops.AddCall(&o.Internal, c.ops, c.start, c.end)
   182  }
   183  
   184  func (r InvalidateOp) Add(o *Ops) {
   185  	data := ops.Write(&o.Internal, ops.TypeRedrawLen)
   186  	data[0] = byte(ops.TypeInvalidate)
   187  	bo := binary.LittleEndian
   188  	// UnixNano cannot represent the zero time.
   189  	if t := r.At; !t.IsZero() {
   190  		nanos := t.UnixNano()
   191  		if nanos > 0 {
   192  			bo.PutUint64(data[1:], uint64(nanos))
   193  		}
   194  	}
   195  }
   196  
   197  // Offset converts an offset to a TransformOp.
   198  func Offset(off image.Point) TransformOp {
   199  	offf := f32.Pt(float32(off.X), float32(off.Y))
   200  	return Affine(f32.Affine2D{}.Offset(offf))
   201  }
   202  
   203  // Affine creates a TransformOp representing the transformation a.
   204  func Affine(a f32.Affine2D) TransformOp {
   205  	return TransformOp{t: a}
   206  }
   207  
   208  // Push the current transformation to the stack and then multiply the
   209  // current transformation with t.
   210  func (t TransformOp) Push(o *Ops) TransformStack {
   211  	id, macroID := ops.PushOp(&o.Internal, ops.TransStack)
   212  	t.add(o, true)
   213  	return TransformStack{ops: &o.Internal, id: id, macroID: macroID}
   214  }
   215  
   216  // Add is like Push except it doesn't push the current transformation to the
   217  // stack.
   218  func (t TransformOp) Add(o *Ops) {
   219  	t.add(o, false)
   220  }
   221  
   222  func (t TransformOp) add(o *Ops, push bool) {
   223  	data := ops.Write(&o.Internal, ops.TypeTransformLen)
   224  	data[0] = byte(ops.TypeTransform)
   225  	if push {
   226  		data[1] = 1
   227  	}
   228  	bo := binary.LittleEndian
   229  	a, b, c, d, e, f := t.t.Elems()
   230  	bo.PutUint32(data[2:], math.Float32bits(a))
   231  	bo.PutUint32(data[2+4*1:], math.Float32bits(b))
   232  	bo.PutUint32(data[2+4*2:], math.Float32bits(c))
   233  	bo.PutUint32(data[2+4*3:], math.Float32bits(d))
   234  	bo.PutUint32(data[2+4*4:], math.Float32bits(e))
   235  	bo.PutUint32(data[2+4*5:], math.Float32bits(f))
   236  }
   237  
   238  func (t TransformStack) Pop() {
   239  	ops.PopOp(t.ops, ops.TransStack, t.id, t.macroID)
   240  	data := ops.Write(t.ops, ops.TypePopTransformLen)
   241  	data[0] = byte(ops.TypePopTransform)
   242  }