op.Offset is a convenience function most often used by layouts. Layouts
usually operate in integer coordinates, and the float32 version of op.Offset
needlessly force conversions from int to float32. This change makes op.Offset
take integer coordinates, to better match its intended use.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
A recorded macro is prefixed with an internal macro op that stores
the end of the macro. The end is used to efficiently skip the macro
when not calling it. The call a macro, the CallOp stores the start
position of the macro.
To support seamless wrapping of Ops lists, this change removes the
dependency on the macro op prefix from CallOp. Internal code can
now call an Ops like this:
var ops op.Ops
var wrapper op.Ops
ops.AddCall(&wrapper.Internal, &ops.Internal, ops.PC{}, ops.PCFor(&ops.Internal))
References: https://todo.sr.ht/~eliasnaur/gio/318
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Remove unnecessary fill when starting a recording in op.Record.
Have the exact number of possible stack kinds in ops.Ops.
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
op.Save, op.SaveStack were compatibility stop-gaps.
API change. Most op.Save/Loads can safely be removed altogether, or
replaced with
trans := op.Offset(f32.Pt(0, 0).Push(gtx.Ops)
...
trans.Pop()
cl := clip.Rect(image.Rect(-1e6, -1e6, 1e6, 1e6)).Push(gtx.Ops)
...
cl.Pop()
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Ops is in the internal package ops, but external clients can reach its
method through op.Ops.Internal. Hide them by converting them to internal
package functions.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The op.Save and Load methods exist to support the need for
transformation, clip, pointer area state to behave as stacks. For
example, layout needs to apply an offset to its children but not
subsequent operations.
Before this change, op.Save and Load were used to save and restore the
state:
ops := new(op.Ops)
// Save state.
state := op.Save(ops)
// Apply offset.
op.Offset(...).Add(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
state.Load()
A drawback with the op.Save mechanism is that there is no direct
connection between the state change and the saving and loading of state.
This causes confusion as to when a Save/Load is needed and who is
responsible for performing them, which leads to subtle bugs and over-use
of Save/Loads.
This change gets rid of the general state stack and replaces it with
per-state stacks. There is now a stack for transformation, clip, pointer
areas, and they can only be restored by the code pushing state to them.
The example above now becomes:
ops := new(op.Ops)
// Push offset to the transformation stack.
stack := op.Offset(...).Push(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
stack.Pop()
For convenience, transformation also be Add'ed if the stack operation is
not required.
Simple state such as the current material no longer has a way to be
restored; it is assumed the client of a PaintOp adds their desired
material operation before it.
API change: replace op.Save/Load with explicit Push/Pop scopes for
op.TransformOps, pointer.AreaOps, clip.Ops.
To ease porting, this change retains a version of op.Save/Load that
saves and restores the transformation and clip stacks. It also retains
an Add method for clip.Op.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
It turns out restoring all operation state from the moment Defer
is executed is too much; for example, a right-click pop-up needs
the transformation, but not the current clip.
Change Defer to only restore the transformation, and reset all
other state.
Other combinations may be needed in future; we'll deal with them then,
possibly by exposing the load state mask.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The semantics were relaxed in a previous commit; this change renames
to operations accordingly.
API change. Use gofmt to adjust your code accordingly:
gofmt -r 'op.Push(a).Pop() -> op.Save(a).Load()'
gofmt -r 'op.Push(a) -> op.Save(a)'
gofmt -r 'v.Pop() -> v.Load()'
gofmt -r 'op.StackOp -> op.StateOp'
Signed-off-by: Elias Naur <mail@eliasnaur.com>
A previous change implemented save/restore semantics where a saved
state can be restored anywhere, not just in a stack-like manner.
This change similarly relaxes the exported Push/Pop operations; the next
change will rename them accordingly.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Push/Pop only allows saving and restoring operation state in a
stack-like manner. We're going to need restoring arbitrary state
for implementing deferred operations.
Generalize state save/restore and implement Push and Pop on top of
that.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
...interface{} requires constructing a slice, which is slow.
This cuts about 100ns from RRect and Border benchmark.
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
Use op.Offset instead, or create and manipulate a f32.Affine2D.
API change. Update your code with a gofmt rule:
gofmt -r 'op.TransformOp{}.Offset -> op.Offset'
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Add support for affine transformations. The key changes are outlined
below.
- Painting/clipping with rectangles is handled by, for complex
transforms, creating clipping paths representing the transformed
rectangle and using a larger bounding box. Cover/Blit shaders updated
correspondingly to correctly map texture cordinates from the new
bounding boxes.
- Since path splitting must happen on CPU the transforms must happen CPU
side as well - offsets removed from shaders.
- Complex transforms will lead to different path splitting which means
that GPU arrays can no longer be cached if the transform has changed.
Thus the current transform is added as a key to the cache.
- Add a public API to op for setting Affine transformations.
There are a number of optimizations that could be explored further but
which are left out now:
- Caching also of CPU operations (e.g path splitting & transforms) and
not only caching the GPU arrays.
- Allow for re-use of cached GPU vertices if the transformation change
is a pure offset / scaling since the splitting is then the same.
Signed-off-by: Viktor <viktor.ogeman@gmail.com>
Encode TransformOp as an Affince2D matrix instead and use that in gpu and io transform handling.
There are no changes to user facing API and so far only the offset part of the matrix is used.
This patch is a step towards full affine transformations.
Signed-off-by: Viktor <viktor.ogeman@gmail.com>
Converting
macro := op.Record(ops)
...
macro.Stop()
macro.Add()
to
macro := op.Record(ops)
...
call := macro.Stop()
call.Add(ops)
Which is more general (call.Add can take a different ops than the op.Record
that started it), and enforced the order between Stop and the subsequent Add.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The only mutable field is "recording", which is used for a sanity
check. THat check is performed (less generally) by Ops.macroStack.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
To match Record, we'd like Push to return a value. To do that and
support the one-line
defer op.Push(ops).Pop()
Pop needs to use a value receiver as well. Drop the active field
and make it so. The field was only a sanity check, a check which is
already done by Ops.stackStack, albeit with a less specific panic.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The funcs replace stack.Push and macro.Record, which become private.
This makes stack and macro faster to write, in particular for stacks
where you can just write the following line to save and restore the
state :
defer op.Push(ops).Pop()
This usage requires Push to return a pointer (since Pop has a pointer
receiver), or else the code doesn't compile.
For consistancy, I tried to do the same for op.Record, but this implied
to turn all the MacroOp fields into pointers, and this caused some
panics. As a result, op.Record doesn't return a pointer.
An other side effect pointed by Larry Clapp: StackOp and MacroOp are not
re-usable any more, you have to allocate a new one for each usage, using
the described funcs above.
Signed-off-by: Thomas Bruyelle <thomas.bruyelle@gmail.com>
The ability to invoke other operation lists belongs in the new CallOp.
While we're here, make MacroOp.Add use a pointer receiver to match the
other methods.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
We'd like to improve the API of Flex, Stack and similar layouts
that use MacroOps internall. Unfortunately, the
func (m MacroOp) Add(o *Ops)
method causes the MacroOp to be allocated on the heap, ruining the
nice garbage-free property of layouts.
Fortunately, layouts don't need the feature that caused the heap
allocation: invoking operation lists different than the current.
CallOp separates the invoke-different-list semantic from MacroOp,
in preparation for removing the feature from MacroOp.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Assign (per-frame) unique ids to each MacroOp and StackOp operations
and ensure that the pairwise Push/Pop and Record/End match.
In a follow-up change the Flex and Stack layouts will rely on those
checks to avoid being overlapped.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
By returning the allocated data buffer, Ops can become an interface
in a future change without forcing operations to allocate.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Instead of adding an implicit ClipOp, return a ClipOp ready to use, freeing the
caller from recording a macro.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Package ui is now only about units except for the Config.Now method.
Remove Now and rename Config to Converter. Add layout.Config to
replace the old ui.Config.
Signed-off-by: Elias Naur <mail@eliasnaur.com>