aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/op/op.go
blob: 174d318552ba3c2fe6f3d7ca9db8f4f65b1457eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// SPDX-License-Identifier: Unlicense OR MIT

/*

Package op implements operations for updating a user interface.

Gio programs use operations, or ops, for describing their user
interfaces. There are operations for drawing, defining input
handlers, changing window properties as well as operations for
controlling the execution of other operations.

Ops represents a list of operations. The most important use
for an Ops list is to describe a complete user interface update
to a ui/app.Window's Update method.

Drawing a colored square:

	import "gioui.org/unit"
	import "gioui.org/app"
	import "gioui.org/op/paint"

	var w app.Window
	var e system.FrameEvent
	ops := new(op.Ops)
	...
	ops.Reset()
	paint.ColorOp{Color: ...}.Add(ops)
	paint.PaintOp{Rect: ...}.Add(ops)
	e.Frame(ops)

State

An Ops list can be viewed as a very simple virtual machine: it has an implicit
mutable state stack and execution flow can be controlled with macros.

The StackOp saves the current state to the state stack and restores it later:

	ops := new(op.Ops)
	// Save the current state, in particular the transform.
	stack := op.Push(ops)
	// Apply a transform to subsequent operations.
	op.Offset(...).Add(ops)
	...
	// Restore the previous transform.
	stack.Pop()

You can also use this one-line to save the current state and restore it at the
end of a function :

  defer op.Push(ops).Pop()

The MacroOp records a list of operations to be executed later:

	ops := new(op.Ops)
	macro := op.Record(ops)
	// Record operations by adding them.
	op.InvalidateOp{}.Add(ops)
	...
	// End recording.
	call := macro.Stop()

	// replay the recorded operations:
	call.Add(ops)

*/
package op

import (
	"encoding/binary"
	"math"
	"time"

	"gioui.org/f32"
	"gioui.org/internal/opconst"
)

// Ops holds a list of operations. Operations are stored in
// serialized form to avoid garbage during construction of
// the ops list.
type Ops struct {
	// version is incremented at each Reset.
	version int
	// data contains the serialized operations.
	data []byte
	// External references for operations.
	refs []interface{}

	stackStack stack
	macroStack stack
}

// StackOp saves and restores the operation state
// in a stack-like manner.
type StackOp struct {
	id      stackID
	macroID int
	ops     *Ops
}

// MacroOp records a list of operations for later use.
type MacroOp struct {
	ops *Ops
	id  stackID
	pc  pc
}

// CallOp invokes the operations recorded by Record.
type CallOp struct {
	// Ops is the list of operations to invoke.
	ops *Ops
	pc  pc
}

// InvalidateOp requests a redraw at the given time. Use
// the zero value to request an immediate redraw.
type InvalidateOp struct {
	At time.Time
}

// TransformOp applies a transform to the current transform. The zero value
// for TransformOp represents the identity transform.
type TransformOp struct {
	t f32.Affine2D
}

// stack tracks the integer identities of StackOp and MacroOp
// operations to ensure correct pairing of Push/Pop and Record/End.
type stack struct {
	currentID int
	nextID    int
}

type stackID struct {
	id   int
	prev int
}

type pc struct {
	data int
	refs int
}

// Push (save) the current operations state.
func Push(o *Ops) StackOp {
	s := StackOp{
		ops:     o,
		id:      o.stackStack.push(),
		macroID: o.macroStack.currentID,
	}
	data := o.Write(opconst.TypePushLen)
	data[0] = byte(opconst.TypePush)
	return s
}

// Pop (restore) a previously Pushed operations state.
func (s StackOp) Pop() {
	if s.ops.macroStack.currentID != s.macroID {
		panic("pop in a different macro than push")
	}
	s.ops.stackStack.pop(s.id)
	data := s.ops.Write(opconst.TypePopLen)
	data[0] = byte(opconst.TypePop)
}

// Reset the Ops, preparing it for re-use. Reset invalidates
// any recorded macros.
func (o *Ops) Reset() {
	o.stackStack = stack{}
	o.macroStack = stack{}
	// Leave references to the GC.
	for i := range o.refs {
		o.refs[i] = nil
	}
	o.data = o.data[:0]
	o.refs = o.refs[:0]
	o.version++
}

// Data is for internal use only.
func (o *Ops) Data() []byte {
	return o.data
}

// Refs is for internal use only.
func (o *Ops) Refs() []interface{} {
	return o.refs
}

// Version is for internal use only.
func (o *Ops) Version() int {
	return o.version
}

// Write is for internal use only.
func (o *Ops) Write(n int, refs ...interface{}) []byte {
	o.data = append(o.data, make([]byte, n)...)
	o.refs = append(o.refs, refs...)
	return o.data[len(o.data)-n:]
}

func (o *Ops) pc() pc {
	return pc{data: len(o.data), refs: len(o.refs)}
}

// Record a macro of operations.
func Record(o *Ops) MacroOp {
	m := MacroOp{
		ops: o,
		id:  o.macroStack.push(),
		pc:  o.pc(),
	}
	// Reserve room for a macro definition. Updated in Stop.
	m.ops.Write(opconst.TypeMacroLen)
	m.fill()
	return m
}

// Stop ends a previously started recording and returns an
// operation for replaying it.
func (m MacroOp) Stop() CallOp {
	m.ops.macroStack.pop(m.id)
	m.fill()
	return CallOp{
		ops: m.ops,
		pc:  m.pc,
	}
}

func (m MacroOp) fill() {
	pc := m.ops.pc()
	// Fill out the macro definition reserved in Record.
	data := m.ops.data[m.pc.data:]
	data = data[:opconst.TypeMacroLen]
	data[0] = byte(opconst.TypeMacro)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], uint32(pc.data))
	bo.PutUint32(data[5:], uint32(pc.refs))
}

// Add the recorded list of operations. Add
// panics if the Ops containing the recording
// has been reset.
func (c CallOp) Add(o *Ops) {
	if c.ops == nil {
		return
	}
	data := o.Write(opconst.TypeCallLen, c.ops)
	data[0] = byte(opconst.TypeCall)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], uint32(c.pc.data))
	bo.PutUint32(data[5:], uint32(c.pc.refs))
}

func (r InvalidateOp) Add(o *Ops) {
	data := o.Write(opconst.TypeRedrawLen)
	data[0] = byte(opconst.TypeInvalidate)
	bo := binary.LittleEndian
	// UnixNano cannot represent the zero time.
	if t := r.At; !t.IsZero() {
		nanos := t.UnixNano()
		if nanos > 0 {
			bo.PutUint64(data[1:], uint64(nanos))
		}
	}
}

// Offset creates a TransformOp with the offset o.
func Offset(o f32.Point) TransformOp {
	return TransformOp{t: f32.Affine2D{}.Offset(o)}
}

// Affine creates a TransformOp representing the transformation a.
func Affine(a f32.Affine2D) TransformOp {
	return TransformOp{t: a}
}

func (t TransformOp) Add(o *Ops) {
	data := o.Write(opconst.TypeTransformLen)
	data[0] = byte(opconst.TypeTransform)
	bo := binary.LittleEndian
	a, b, c, d, e, f := t.t.Elems()
	bo.PutUint32(data[1:], math.Float32bits(a))
	bo.PutUint32(data[1+4*1:], math.Float32bits(b))
	bo.PutUint32(data[1+4*2:], math.Float32bits(c))
	bo.PutUint32(data[1+4*3:], math.Float32bits(d))
	bo.PutUint32(data[1+4*4:], math.Float32bits(e))
	bo.PutUint32(data[1+4*5:], math.Float32bits(f))
}

func (s *stack) push() stackID {
	s.nextID++
	sid := stackID{
		id:   s.nextID,
		prev: s.currentID,
	}
	s.currentID = s.nextID
	return sid
}

func (s *stack) check(sid stackID) {
	if s.currentID != sid.id {
		panic("unbalanced operation")
	}
}

func (s *stack) pop(sid stackID) {
	s.check(sid)
	s.currentID = sid.prev
}