op.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. /*
  3. Package op implements operations for updating a user interface.
  4. Gio programs use operations, or ops, for describing their user
  5. interfaces. There are operations for drawing, defining input
  6. handlers, changing window properties as well as operations for
  7. controlling the execution of other operations.
  8. Ops represents a list of operations. The most important use
  9. for an Ops list is to describe a complete user interface update
  10. to a ui/app.Window's Update method.
  11. Drawing a colored square:
  12. import "gioui.org/unit"
  13. import "gioui.org/app"
  14. import "gioui.org/op/paint"
  15. var w app.Window
  16. var e system.FrameEvent
  17. ops := new(op.Ops)
  18. ...
  19. ops.Reset()
  20. paint.ColorOp{Color: ...}.Add(ops)
  21. paint.PaintOp{Rect: ...}.Add(ops)
  22. e.Frame(ops)
  23. # State
  24. An Ops list can be viewed as a very simple virtual machine: it has state such
  25. as transformation and color and execution flow can be controlled with macros.
  26. Some state, such as the current color, is modified directly by operations with
  27. Add methods. Other state, such as transformation and clip shape, are
  28. represented by stacks.
  29. This example sets the simple color state and pushes an offset to the
  30. transformation stack.
  31. ops := new(op.Ops)
  32. // Set the color.
  33. paint.ColorOp{...}.Add(ops)
  34. // Apply an offset to subsequent operations.
  35. stack := op.Offset(...).Push(ops)
  36. ...
  37. // Undo the offset transformation.
  38. stack.Pop()
  39. The MacroOp records a list of operations to be executed later:
  40. ops := new(op.Ops)
  41. macro := op.Record(ops)
  42. // Record operations by adding them.
  43. ...
  44. // End recording.
  45. call := macro.Stop()
  46. // replay the recorded operations:
  47. call.Add(ops)
  48. */
  49. package op
  50. import (
  51. "encoding/binary"
  52. "image"
  53. "math"
  54. "time"
  55. "gioui.org/f32"
  56. "gioui.org/internal/ops"
  57. )
  58. // Ops holds a list of operations. Operations are stored in
  59. // serialized form to avoid garbage during construction of
  60. // the ops list.
  61. type Ops struct {
  62. // Internal is for internal use, despite being exported.
  63. Internal ops.Ops
  64. }
  65. // MacroOp records a list of operations for later use.
  66. type MacroOp struct {
  67. ops *ops.Ops
  68. id ops.StackID
  69. pc ops.PC
  70. }
  71. // CallOp invokes the operations recorded by Record.
  72. type CallOp struct {
  73. // Ops is the list of operations to invoke.
  74. ops *ops.Ops
  75. start ops.PC
  76. end ops.PC
  77. }
  78. // InvalidateCmd requests a redraw at the given time. Use
  79. // the zero value to request an immediate redraw.
  80. type InvalidateCmd struct {
  81. At time.Time
  82. }
  83. // TransformOp represents a transformation that can be pushed on the
  84. // transformation stack.
  85. type TransformOp struct {
  86. t f32.Affine2D
  87. }
  88. // TransformStack represents a TransformOp pushed on the transformation stack.
  89. type TransformStack struct {
  90. id ops.StackID
  91. macroID uint32
  92. ops *ops.Ops
  93. }
  94. // Defer executes c after all other operations have completed, including
  95. // previously deferred operations.
  96. // Defer saves the transformation stack and pushes it prior to executing
  97. // c. All other operation state is reset.
  98. //
  99. // Note that deferred operations are executed in first-in-first-out order,
  100. // unlike the Go facility of the same name.
  101. func Defer(o *Ops, c CallOp) {
  102. if c.ops == nil {
  103. return
  104. }
  105. state := ops.Save(&o.Internal)
  106. // Wrap c in a macro that loads the saved state before execution.
  107. m := Record(o)
  108. state.Load()
  109. c.Add(o)
  110. c = m.Stop()
  111. // A Defer is recorded as a TypeDefer followed by the
  112. // wrapped macro.
  113. data := ops.Write(&o.Internal, ops.TypeDeferLen)
  114. data[0] = byte(ops.TypeDefer)
  115. c.Add(o)
  116. }
  117. // Reset the Ops, preparing it for re-use. Reset invalidates
  118. // any recorded macros.
  119. func (o *Ops) Reset() {
  120. ops.Reset(&o.Internal)
  121. }
  122. // Record a macro of operations.
  123. func Record(o *Ops) MacroOp {
  124. m := MacroOp{
  125. ops: &o.Internal,
  126. id: ops.PushMacro(&o.Internal),
  127. pc: ops.PCFor(&o.Internal),
  128. }
  129. // Reserve room for a macro definition. Updated in Stop.
  130. data := ops.Write(m.ops, ops.TypeMacroLen)
  131. data[0] = byte(ops.TypeMacro)
  132. return m
  133. }
  134. // Stop ends a previously started recording and returns an
  135. // operation for replaying it.
  136. func (m MacroOp) Stop() CallOp {
  137. ops.PopMacro(m.ops, m.id)
  138. ops.FillMacro(m.ops, m.pc)
  139. return CallOp{
  140. ops: m.ops,
  141. // Skip macro header.
  142. start: m.pc.Add(ops.TypeMacro),
  143. end: ops.PCFor(m.ops),
  144. }
  145. }
  146. // Add the recorded list of operations. Add
  147. // panics if the Ops containing the recording
  148. // has been reset.
  149. func (c CallOp) Add(o *Ops) {
  150. if c.ops == nil {
  151. return
  152. }
  153. ops.AddCall(&o.Internal, c.ops, c.start, c.end)
  154. }
  155. // Offset converts an offset to a TransformOp.
  156. func Offset(off image.Point) TransformOp {
  157. offf := f32.Pt(float32(off.X), float32(off.Y))
  158. return Affine(f32.Affine2D{}.Offset(offf))
  159. }
  160. // Affine creates a TransformOp representing the transformation a.
  161. func Affine(a f32.Affine2D) TransformOp {
  162. return TransformOp{t: a}
  163. }
  164. // Push the current transformation to the stack and then multiply the
  165. // current transformation with t.
  166. func (t TransformOp) Push(o *Ops) TransformStack {
  167. id, macroID := ops.PushOp(&o.Internal, ops.TransStack)
  168. t.add(o, true)
  169. return TransformStack{ops: &o.Internal, id: id, macroID: macroID}
  170. }
  171. // Add is like Push except it doesn't push the current transformation to the
  172. // stack.
  173. func (t TransformOp) Add(o *Ops) {
  174. t.add(o, false)
  175. }
  176. func (t TransformOp) add(o *Ops, push bool) {
  177. data := ops.Write(&o.Internal, ops.TypeTransformLen)
  178. data[0] = byte(ops.TypeTransform)
  179. if push {
  180. data[1] = 1
  181. }
  182. bo := binary.LittleEndian
  183. a, b, c, d, e, f := t.t.Elems()
  184. bo.PutUint32(data[2:], math.Float32bits(a))
  185. bo.PutUint32(data[2+4*1:], math.Float32bits(b))
  186. bo.PutUint32(data[2+4*2:], math.Float32bits(c))
  187. bo.PutUint32(data[2+4*3:], math.Float32bits(d))
  188. bo.PutUint32(data[2+4*4:], math.Float32bits(e))
  189. bo.PutUint32(data[2+4*5:], math.Float32bits(f))
  190. }
  191. func (t TransformStack) Pop() {
  192. ops.PopOp(t.ops, ops.TransStack, t.id, t.macroID)
  193. data := ops.Write(t.ops, ops.TypePopTransformLen)
  194. data[0] = byte(ops.TypePopTransform)
  195. }
  196. func (InvalidateCmd) ImplementsCommand() {}