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/p9c/p9/pkg/gel/gio/unit"
19 import "github.com/p9c/p9/pkg/gel/gio/app"
20 import "github.com/p9c/p9/pkg/gel/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 Save function saves the current state for later restoring:
37 38 ops := new(op.Ops)
39 // Save the current state, in particular the transform.
40 state := op.Save(ops)
41 // Apply a transform to subsequent operations.
42 op.Offset(...).Add(ops)
43 ...
44 // Restore the previous transform.
45 state.Load()
46 47 You can also use this one-line to save the current state and restore it at the
48 end of a function :
49 50 defer op.Save(ops).Load()
51 52 The MacroOp records a list of operations to be executed later:
53 54 ops := new(op.Ops)
55 macro := op.Record(ops)
56 // Record operations by adding them.
57 op.InvalidateOp{}.Add(ops)
58 ...
59 // End recording.
60 call := macro.Stop()
61 62 // replay the recorded operations:
63 call.Add(ops)
64 65 */
66 package op
67 68 import (
69 "encoding/binary"
70 "math"
71 "time"
72 73 "github.com/p9c/p9/pkg/gel/gio/f32"
74 "github.com/p9c/p9/pkg/gel/gio/internal/opconst"
75 )
76 77 // Ops holds a list of operations. Operations are stored in
78 // serialized form to avoid garbage during construction of
79 // the ops list.
80 type Ops struct {
81 // version is incremented at each Reset.
82 version int
83 // data contains the serialized operations.
84 data []byte
85 // refs hold external references for operations.
86 refs []interface{}
87 // nextStateID is the id allocated for the next
88 // StateOp.
89 nextStateID int
90 91 macroStack stack
92 }
93 94 // StateOp represents a saved operation snapshop to be restored
95 // later.
96 type StateOp struct {
97 id int
98 macroID int
99 ops *Ops
100 }
101 102 // MacroOp records a list of operations for later use.
103 type MacroOp struct {
104 ops *Ops
105 id stackID
106 pc pc
107 }
108 109 // CallOp invokes the operations recorded by Record.
110 type CallOp struct {
111 // Ops is the list of operations to invoke.
112 ops *Ops
113 pc pc
114 }
115 116 // InvalidateOp requests a redraw at the given time. Use
117 // the zero value to request an immediate redraw.
118 type InvalidateOp struct {
119 At time.Time
120 }
121 122 // TransformOp applies a transform to the current transform. The zero value
123 // for TransformOp represents the identity transform.
124 type TransformOp struct {
125 t f32.Affine2D
126 }
127 128 // stack tracks the integer identities of MacroOp
129 // operations to ensure correct pairing of Record/End.
130 type stack struct {
131 currentID int
132 nextID int
133 }
134 135 type stackID struct {
136 id int
137 prev int
138 }
139 140 type pc struct {
141 data int
142 refs int
143 }
144 145 // Defer executes c after all other operations have completed,
146 // including previously deferred operations.
147 // Defer saves the current transformation and restores it prior
148 // to execution. All other operation state is reset.
149 //
150 // Note that deferred operations are executed in first-in-first-out
151 // order, unlike the Go facility of the same name.
152 func Defer(o *Ops, c CallOp) {
153 if c.ops == nil {
154 return
155 }
156 state := Save(o)
157 // Wrap c in a macro that loads the saved state before execution.
158 m := Record(o)
159 load(o, opconst.InitialStateID, opconst.AllState)
160 load(o, state.id, opconst.TransformState)
161 c.Add(o)
162 c = m.Stop()
163 // A Defer is recorded as a TypeDefer followed by the
164 // wrapped macro.
165 data := o.Write(opconst.TypeDeferLen)
166 data[0] = byte(opconst.TypeDefer)
167 c.Add(o)
168 }
169 170 // Save the current operations state.
171 func Save(o *Ops) StateOp {
172 o.nextStateID++
173 s := StateOp{
174 ops: o,
175 id: o.nextStateID,
176 macroID: o.macroStack.currentID,
177 }
178 save(o, s.id)
179 return s
180 }
181 182 // save records a save of the operations state to
183 // id.
184 func save(o *Ops, id int) {
185 bo := binary.LittleEndian
186 data := o.Write(opconst.TypeSaveLen)
187 data[0] = byte(opconst.TypeSave)
188 bo.PutUint32(data[1:], uint32(id))
189 }
190 191 // Load a previously saved operations state.
192 func (s StateOp) Load() {
193 if s.ops.macroStack.currentID != s.macroID {
194 panic("load in a different macro than save")
195 }
196 if s.id == 0 {
197 panic("zero-value op")
198 }
199 load(s.ops, s.id, opconst.AllState)
200 }
201 202 // load a previously saved operations state given
203 // its ID. Only state included in mask is affected.
204 func load(o *Ops, id int, m opconst.StateMask) {
205 bo := binary.LittleEndian
206 data := o.Write(opconst.TypeLoadLen)
207 data[0] = byte(opconst.TypeLoad)
208 data[1] = byte(m)
209 bo.PutUint32(data[2:], uint32(id))
210 }
211 212 // Reset the Ops, preparing it for re-use. Reset invalidates
213 // any recorded macros.
214 func (o *Ops) Reset() {
215 o.macroStack = stack{}
216 // Leave references to the GC.
217 for i := range o.refs {
218 o.refs[i] = nil
219 }
220 o.data = o.data[:0]
221 o.refs = o.refs[:0]
222 o.nextStateID = 0
223 o.version++
224 }
225 226 // Data is for internal use only.
227 func (o *Ops) Data() []byte {
228 return o.data
229 }
230 231 // Refs is for internal use only.
232 func (o *Ops) Refs() []interface{} {
233 return o.refs
234 }
235 236 // Version is for internal use only.
237 func (o *Ops) Version() int {
238 return o.version
239 }
240 241 // Write is for internal use only.
242 func (o *Ops) Write(n int) []byte {
243 o.data = append(o.data, make([]byte, n)...)
244 return o.data[len(o.data)-n:]
245 }
246 247 // Write1 is for internal use only.
248 func (o *Ops) Write1(n int, ref1 interface{}) []byte {
249 o.data = append(o.data, make([]byte, n)...)
250 o.refs = append(o.refs, ref1)
251 return o.data[len(o.data)-n:]
252 }
253 254 // Write2 is for internal use only.
255 func (o *Ops) Write2(n int, ref1, ref2 interface{}) []byte {
256 o.data = append(o.data, make([]byte, n)...)
257 o.refs = append(o.refs, ref1, ref2)
258 return o.data[len(o.data)-n:]
259 }
260 261 func (o *Ops) pc() pc {
262 return pc{data: len(o.data), refs: len(o.refs)}
263 }
264 265 // Record a macro of operations.
266 func Record(o *Ops) MacroOp {
267 m := MacroOp{
268 ops: o,
269 id: o.macroStack.push(),
270 pc: o.pc(),
271 }
272 // Reserve room for a macro definition. Updated in Stop.
273 m.ops.Write(opconst.TypeMacroLen)
274 m.fill()
275 return m
276 }
277 278 // Stop ends a previously started recording and returns an
279 // operation for replaying it.
280 func (m MacroOp) Stop() CallOp {
281 m.ops.macroStack.pop(m.id)
282 m.fill()
283 return CallOp{
284 ops: m.ops,
285 pc: m.pc,
286 }
287 }
288 289 func (m MacroOp) fill() {
290 pc := m.ops.pc()
291 // Fill out the macro definition reserved in Record.
292 data := m.ops.data[m.pc.data:]
293 data = data[:opconst.TypeMacroLen]
294 data[0] = byte(opconst.TypeMacro)
295 bo := binary.LittleEndian
296 bo.PutUint32(data[1:], uint32(pc.data))
297 bo.PutUint32(data[5:], uint32(pc.refs))
298 }
299 300 // Add the recorded list of operations. Add
301 // panics if the Ops containing the recording
302 // has been reset.
303 func (c CallOp) Add(o *Ops) {
304 if c.ops == nil {
305 return
306 }
307 data := o.Write1(opconst.TypeCallLen, c.ops)
308 data[0] = byte(opconst.TypeCall)
309 bo := binary.LittleEndian
310 bo.PutUint32(data[1:], uint32(c.pc.data))
311 bo.PutUint32(data[5:], uint32(c.pc.refs))
312 }
313 314 func (r InvalidateOp) Add(o *Ops) {
315 data := o.Write(opconst.TypeRedrawLen)
316 data[0] = byte(opconst.TypeInvalidate)
317 bo := binary.LittleEndian
318 // UnixNano cannot represent the zero time.
319 if t := r.At; !t.IsZero() {
320 nanos := t.UnixNano()
321 if nanos > 0 {
322 bo.PutUint64(data[1:], uint64(nanos))
323 }
324 }
325 }
326 327 // Offset creates a TransformOp with the offset o.
328 func Offset(o f32.Point) TransformOp {
329 return TransformOp{t: f32.Affine2D{}.Offset(o)}
330 }
331 332 // Affine creates a TransformOp representing the transformation a.
333 func Affine(a f32.Affine2D) TransformOp {
334 return TransformOp{t: a}
335 }
336 337 func (t TransformOp) Add(o *Ops) {
338 data := o.Write(opconst.TypeTransformLen)
339 data[0] = byte(opconst.TypeTransform)
340 bo := binary.LittleEndian
341 a, b, c, d, e, f := t.t.Elems()
342 bo.PutUint32(data[1:], math.Float32bits(a))
343 bo.PutUint32(data[1+4*1:], math.Float32bits(b))
344 bo.PutUint32(data[1+4*2:], math.Float32bits(c))
345 bo.PutUint32(data[1+4*3:], math.Float32bits(d))
346 bo.PutUint32(data[1+4*4:], math.Float32bits(e))
347 bo.PutUint32(data[1+4*5:], math.Float32bits(f))
348 }
349 350 func (s *stack) push() stackID {
351 s.nextID++
352 sid := stackID{
353 id: s.nextID,
354 prev: s.currentID,
355 }
356 s.currentID = s.nextID
357 return sid
358 }
359 360 func (s *stack) check(sid stackID) {
361 if s.currentID != sid.id {
362 panic("unbalanced operation")
363 }
364 }
365 366 func (s *stack) pop(sid stackID) {
367 s.check(sid)
368 s.currentID = sid.prev
369 }
370