github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/op/op.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  /*
     4  
     5  Package op implements operations for updating a user interface.
     6  
     7  Gio programs use operations, or ops, for describing their user
     8  interfaces. There are operations for drawing, defining input
     9  handlers, changing window properties as well as operations for
    10  controlling the execution of other operations.
    11  
    12  Ops represents a list of operations. The most important use
    13  for an Ops list is to describe a complete user interface update
    14  to a ui/app.Window's Update method.
    15  
    16  Drawing a colored square:
    17  
    18  	import "github.com/gop9/olt/gio/unit"
    19  	import "github.com/gop9/olt/gio/app"
    20  	import "github.com/gop9/olt/gio/op/paint"
    21  
    22  	var w app.Window
    23  	var e system.FrameEvent
    24  	ops := new(op.Ops)
    25  	...
    26  	ops.Reset()
    27  	paint.ColorOp{Color: ...}.Add(ops)
    28  	paint.PaintOp{Rect: ...}.Add(ops)
    29  	e.Frame(ops)
    30  
    31  State
    32  
    33  An Ops list can be viewed as a very simple virtual machine: it has an implicit
    34  mutable state stack and execution flow can be controlled with macros.
    35  
    36  The StackOp saves the current state to the state stack and restores it later:
    37  
    38  	ops := new(op.Ops)
    39  	var stack op.StackOp
    40  	// Save the current state, in particular the transform.
    41  	stack.Push(ops)
    42  	// Apply a transform to subsequent operations.
    43  	op.TransformOp{}.Offset(...).Add(ops)
    44  	...
    45  	// Restore the previous transform.
    46  	stack.Pop()
    47  
    48  The CallOp invokes another operation list:
    49  
    50  	ops := new(op.Ops)
    51  	ops2 := new(op.Ops)
    52  	op.CallOp{Ops: ops2}.Add(ops)
    53  
    54  The MacroOp records a list of operations to be executed later:
    55  
    56  	ops := new(op.Ops)
    57  	var macro op.MacroOp
    58  	macro.Record(ops)
    59  	// Record operations by adding them.
    60  	op.InvalidateOp{}.Add(ops)
    61  	...
    62  	// End recording.
    63  	macro.Stop()
    64  
    65  	// replay the recorded operations by calling Add:
    66  	macro.Add()
    67  
    68  */
    69  package op
    70  
    71  import (
    72  	"encoding/binary"
    73  	"math"
    74  	"time"
    75  
    76  	"github.com/gop9/olt/gio/f32"
    77  	"github.com/gop9/olt/gio/internal/opconst"
    78  )
    79  
    80  // Ops holds a list of operations. Operations are stored in
    81  // serialized form to avoid garbage during construction of
    82  // the ops list.
    83  type Ops struct {
    84  	// version is incremented at each Reset.
    85  	version int
    86  	// data contains the serialized operations.
    87  	data []byte
    88  	// External references for operations.
    89  	refs []interface{}
    90  
    91  	stackStack stack
    92  	macroStack stack
    93  }
    94  
    95  // StackOp saves and restores the operation state
    96  // in a stack-like manner.
    97  type StackOp struct {
    98  	stackDepth int
    99  	id         stackID
   100  	macroID    int
   101  	active     bool
   102  	ops        *Ops
   103  }
   104  
   105  // MacroOp records a list of operations for later use.
   106  type MacroOp struct {
   107  	recording bool
   108  	ops       *Ops
   109  	id        stackID
   110  	pc        pc
   111  }
   112  
   113  // CallOp invokes all the operations from a separate
   114  // operations list.
   115  type CallOp struct {
   116  	// Ops is the list of operations to invoke.
   117  	Ops *Ops
   118  }
   119  
   120  // InvalidateOp requests a redraw at the given time. Use
   121  // the zero value to request an immediate redraw.
   122  type InvalidateOp struct {
   123  	At time.Time
   124  }
   125  
   126  // TransformOp applies a transform to the current transform.
   127  type TransformOp struct {
   128  	// TODO: general transformations.
   129  	offset f32.Point
   130  }
   131  
   132  // stack tracks the integer identities of StackOp and MacroOp
   133  // operations to ensure correct pairing of Push/Pop and Record/End.
   134  type stack struct {
   135  	currentID int
   136  	nextID    int
   137  }
   138  
   139  type stackID struct {
   140  	id   int
   141  	prev int
   142  }
   143  
   144  type pc struct {
   145  	data int
   146  	refs int
   147  }
   148  
   149  // Add the call to the operation list.
   150  func (c CallOp) Add(o *Ops) {
   151  	if c.Ops == nil {
   152  		return
   153  	}
   154  	data := o.Write(opconst.TypeCallLen, c.Ops)
   155  	data[0] = byte(opconst.TypeCall)
   156  }
   157  
   158  // Push (save) the current operations state.
   159  func (s *StackOp) Push(o *Ops) {
   160  	if s.active {
   161  		panic("unbalanced push")
   162  	}
   163  	s.active = true
   164  	s.ops = o
   165  	s.id = o.stackStack.push()
   166  	s.macroID = o.macroStack.currentID
   167  	data := o.Write(opconst.TypePushLen)
   168  	data[0] = byte(opconst.TypePush)
   169  }
   170  
   171  // Pop (restore) a previously Pushed operations state.
   172  func (s *StackOp) Pop() {
   173  	if !s.active {
   174  		panic("unbalanced pop")
   175  	}
   176  	if s.ops.macroStack.currentID != s.macroID {
   177  		panic("pop in a different macro than push")
   178  	}
   179  	s.ops.stackStack.pop(s.id)
   180  	s.active = false
   181  	data := s.ops.Write(opconst.TypePopLen)
   182  	data[0] = byte(opconst.TypePop)
   183  }
   184  
   185  // Reset the Ops, preparing it for re-use.
   186  func (o *Ops) Reset() {
   187  	o.stackStack = stack{}
   188  	o.macroStack = stack{}
   189  	// Leave references to the GC.
   190  	for i := range o.refs {
   191  		o.refs[i] = nil
   192  	}
   193  	o.data = o.data[:0]
   194  	o.refs = o.refs[:0]
   195  	o.version++
   196  }
   197  
   198  // Data is for internal use only.
   199  func (o *Ops) Data() []byte {
   200  	return o.data
   201  }
   202  
   203  // Refs is for internal use only.
   204  func (o *Ops) Refs() []interface{} {
   205  	return o.refs
   206  }
   207  
   208  // Version is for internal use only.
   209  func (o *Ops) Version() int {
   210  	return o.version
   211  }
   212  
   213  // Write is for internal use only.
   214  func (o *Ops) Write(n int, refs ...interface{}) []byte {
   215  	o.data = append(o.data, make([]byte, n)...)
   216  	o.refs = append(o.refs, refs...)
   217  	return o.data[len(o.data)-n:]
   218  }
   219  
   220  func (o *Ops) pc() pc {
   221  	return pc{data: len(o.data), refs: len(o.refs)}
   222  }
   223  
   224  // Record a macro of operations.
   225  func (m *MacroOp) Record(o *Ops) {
   226  	if m.recording {
   227  		panic("already recording")
   228  	}
   229  	m.recording = true
   230  	m.ops = o
   231  	m.id = m.ops.macroStack.push()
   232  	m.pc = o.pc()
   233  	// Reserve room for a macro definition. Updated in Stop.
   234  	m.ops.Write(opconst.TypeMacroDefLen)
   235  	m.fill()
   236  }
   237  
   238  // Stop ends a previously started recording.
   239  func (m *MacroOp) Stop() {
   240  	if !m.recording {
   241  		panic("not recording")
   242  	}
   243  	m.ops.macroStack.pop(m.id)
   244  	m.recording = false
   245  	m.fill()
   246  }
   247  
   248  func (m *MacroOp) fill() {
   249  	pc := m.ops.pc()
   250  	// Fill out the macro definition reserved in Record.
   251  	data := m.ops.data[m.pc.data:]
   252  	data = data[:opconst.TypeMacroDefLen]
   253  	data[0] = byte(opconst.TypeMacroDef)
   254  	bo := binary.LittleEndian
   255  	bo.PutUint32(data[1:], uint32(pc.data))
   256  	bo.PutUint32(data[5:], uint32(pc.refs))
   257  }
   258  
   259  // Add the recorded list of operations.
   260  func (m *MacroOp) Add() {
   261  	if m.recording {
   262  		panic("a recording is in progress")
   263  	}
   264  	if m.ops == nil {
   265  		return
   266  	}
   267  	data := m.ops.Write(opconst.TypeMacroLen)
   268  	data[0] = byte(opconst.TypeMacro)
   269  	bo := binary.LittleEndian
   270  	bo.PutUint32(data[1:], uint32(m.pc.data))
   271  	bo.PutUint32(data[5:], uint32(m.pc.refs))
   272  }
   273  
   274  func (r InvalidateOp) Add(o *Ops) {
   275  	data := o.Write(opconst.TypeRedrawLen)
   276  	data[0] = byte(opconst.TypeInvalidate)
   277  	bo := binary.LittleEndian
   278  	// UnixNano cannot represent the zero time.
   279  	if t := r.At; !t.IsZero() {
   280  		nanos := t.UnixNano()
   281  		if nanos > 0 {
   282  			bo.PutUint64(data[1:], uint64(nanos))
   283  		}
   284  	}
   285  }
   286  
   287  // Offset the transformation.
   288  func (t TransformOp) Offset(o f32.Point) TransformOp {
   289  	return t.Multiply(TransformOp{o})
   290  }
   291  
   292  // Invert the transformation.
   293  func (t TransformOp) Invert() TransformOp {
   294  	return TransformOp{offset: t.offset.Mul(-1)}
   295  }
   296  
   297  // Transform a point.
   298  func (t TransformOp) Transform(p f32.Point) f32.Point {
   299  	return p.Add(t.offset)
   300  }
   301  
   302  // Multiply by a transformation.
   303  func (t TransformOp) Multiply(t2 TransformOp) TransformOp {
   304  	return TransformOp{
   305  		offset: t.offset.Add(t2.offset),
   306  	}
   307  }
   308  
   309  func (t TransformOp) Add(o *Ops) {
   310  	data := o.Write(opconst.TypeTransformLen)
   311  	data[0] = byte(opconst.TypeTransform)
   312  	bo := binary.LittleEndian
   313  	bo.PutUint32(data[1:], math.Float32bits(t.offset.X))
   314  	bo.PutUint32(data[5:], math.Float32bits(t.offset.Y))
   315  }
   316  
   317  func (s *stack) push() stackID {
   318  	s.nextID++
   319  	sid := stackID{
   320  		id:   s.nextID,
   321  		prev: s.currentID,
   322  	}
   323  	s.currentID = s.nextID
   324  	return sid
   325  }
   326  
   327  func (s *stack) check(sid stackID) {
   328  	if s.currentID != sid.id {
   329  		panic("unbalanced operation")
   330  	}
   331  }
   332  
   333  func (s *stack) pop(sid stackID) {
   334  	s.check(sid)
   335  	s.currentID = sid.prev
   336  }