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 }