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 }