mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
ef8171b971
Instead of having to supply the predicates for event filtering at the
time of layout, the new Filter type allows widgets to filter at the time
of calling Source.Events. There is then only the need for a single input
op type, in package event.
Filters most importantly allow the use of one tag for several event types,
and we can define that a widget w has &w as its primary tag, by convention.
This allows the replacement of per-widget Focus methods with direct uses
of FocusCmd{&w}, and the later addition of Source.Focused(&w) queries.
Note that the TestCursor test needed restructuring to avoid its use of
InputOps.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
502 lines
12 KiB
Go
502 lines
12 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package ops
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"image"
|
|
"math"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/internal/byteslice"
|
|
"gioui.org/internal/scene"
|
|
)
|
|
|
|
type Ops struct {
|
|
// version is incremented at each Reset.
|
|
version uint32
|
|
// data contains the serialized operations.
|
|
data []byte
|
|
// refs hold external references for operations.
|
|
refs []interface{}
|
|
// stringRefs provides space for string references, pointers to which will
|
|
// be stored in refs. Storing a string directly in refs would cause a heap
|
|
// allocation, to store the string header in an interface value. The backing
|
|
// array of stringRefs, on the other hand, gets reused between calls to
|
|
// reset, making string references free on average.
|
|
//
|
|
// Appending to stringRefs might reallocate the backing array, which will
|
|
// leave pointers to the old array in refs. This temporarily causes a slight
|
|
// increase in memory usage, but this, too, amortizes away as the capacity
|
|
// of stringRefs approaches its stable maximum.
|
|
stringRefs []string
|
|
// nextStateID is the id allocated for the next
|
|
// StateOp.
|
|
nextStateID uint32
|
|
// multipOp indicates a multi-op such as clip.Path is being added.
|
|
multipOp bool
|
|
|
|
macroStack stack
|
|
stacks [_StackKind]stack
|
|
}
|
|
|
|
type OpType byte
|
|
|
|
type Shape byte
|
|
|
|
// Start at a high number for easier debugging.
|
|
const firstOpIndex = 200
|
|
|
|
const (
|
|
TypeMacro OpType = iota + firstOpIndex
|
|
TypeCall
|
|
TypeDefer
|
|
TypeTransform
|
|
TypePopTransform
|
|
TypePushOpacity
|
|
TypePopOpacity
|
|
TypeInvalidate
|
|
TypeImage
|
|
TypePaint
|
|
TypeColor
|
|
TypeLinearGradient
|
|
TypePass
|
|
TypePopPass
|
|
TypeInput
|
|
TypeKeyInput
|
|
TypeSave
|
|
TypeLoad
|
|
TypeAux
|
|
TypeClip
|
|
TypePopClip
|
|
TypeCursor
|
|
TypePath
|
|
TypeStroke
|
|
TypeSemanticLabel
|
|
TypeSemanticDesc
|
|
TypeSemanticClass
|
|
TypeSemanticSelected
|
|
TypeSemanticEnabled
|
|
TypeActionInput
|
|
)
|
|
|
|
type StackID struct {
|
|
id uint32
|
|
prev uint32
|
|
}
|
|
|
|
// StateOp represents a saved operation snapshot to be restored
|
|
// later.
|
|
type StateOp struct {
|
|
id uint32
|
|
macroID uint32
|
|
ops *Ops
|
|
}
|
|
|
|
// stack tracks the integer identities of stack operations to ensure correct
|
|
// pairing of their push and pop methods.
|
|
type stack struct {
|
|
currentID uint32
|
|
nextID uint32
|
|
}
|
|
|
|
type StackKind uint8
|
|
|
|
// ClipOp is the shadow of clip.Op.
|
|
type ClipOp struct {
|
|
Bounds image.Rectangle
|
|
Outline bool
|
|
Shape Shape
|
|
}
|
|
|
|
const (
|
|
ClipStack StackKind = iota
|
|
TransStack
|
|
PassStack
|
|
OpacityStack
|
|
_StackKind
|
|
)
|
|
|
|
const (
|
|
Path Shape = iota
|
|
Ellipse
|
|
Rect
|
|
)
|
|
|
|
const (
|
|
TypeMacroLen = 1 + 4 + 4
|
|
TypeCallLen = 1 + 4 + 4 + 4 + 4
|
|
TypeDeferLen = 1
|
|
TypeTransformLen = 1 + 1 + 4*6
|
|
TypePopTransformLen = 1
|
|
TypePushOpacityLen = 1 + 4
|
|
TypePopOpacityLen = 1
|
|
TypeRedrawLen = 1 + 8
|
|
TypeImageLen = 1 + 1
|
|
TypePaintLen = 1
|
|
TypeColorLen = 1 + 4
|
|
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
|
TypePassLen = 1
|
|
TypePopPassLen = 1
|
|
TypeInputLen = 1
|
|
TypeKeyInputLen = 1 + 1
|
|
TypeSaveLen = 1 + 4
|
|
TypeLoadLen = 1 + 4
|
|
TypeAuxLen = 1
|
|
TypeClipLen = 1 + 4*4 + 1 + 1
|
|
TypePopClipLen = 1
|
|
TypeCursorLen = 2
|
|
TypePathLen = 8 + 1
|
|
TypeStrokeLen = 1 + 4
|
|
TypeSemanticLabelLen = 1
|
|
TypeSemanticDescLen = 1
|
|
TypeSemanticClassLen = 2
|
|
TypeSemanticSelectedLen = 2
|
|
TypeSemanticEnabledLen = 2
|
|
TypeActionInputLen = 1 + 1
|
|
)
|
|
|
|
func (op *ClipOp) Decode(data []byte) {
|
|
if len(data) < TypeClipLen || OpType(data[0]) != TypeClip {
|
|
panic("invalid op")
|
|
}
|
|
data = data[:TypeClipLen]
|
|
bo := binary.LittleEndian
|
|
op.Bounds.Min.X = int(int32(bo.Uint32(data[1:])))
|
|
op.Bounds.Min.Y = int(int32(bo.Uint32(data[5:])))
|
|
op.Bounds.Max.X = int(int32(bo.Uint32(data[9:])))
|
|
op.Bounds.Max.Y = int(int32(bo.Uint32(data[13:])))
|
|
op.Outline = data[17] == 1
|
|
op.Shape = Shape(data[18])
|
|
}
|
|
|
|
func Reset(o *Ops) {
|
|
o.macroStack = stack{}
|
|
o.stacks = [_StackKind]stack{}
|
|
// Leave references to the GC.
|
|
for i := range o.refs {
|
|
o.refs[i] = nil
|
|
}
|
|
for i := range o.stringRefs {
|
|
o.stringRefs[i] = ""
|
|
}
|
|
o.data = o.data[:0]
|
|
o.refs = o.refs[:0]
|
|
o.stringRefs = o.stringRefs[:0]
|
|
o.nextStateID = 0
|
|
o.version++
|
|
}
|
|
|
|
func Write(o *Ops, n int) []byte {
|
|
if o.multipOp {
|
|
panic("cannot mix multi ops with single ones")
|
|
}
|
|
o.data = append(o.data, make([]byte, n)...)
|
|
return o.data[len(o.data)-n:]
|
|
}
|
|
|
|
func BeginMulti(o *Ops) {
|
|
if o.multipOp {
|
|
panic("cannot interleave multi ops")
|
|
}
|
|
o.multipOp = true
|
|
}
|
|
|
|
func EndMulti(o *Ops) {
|
|
if !o.multipOp {
|
|
panic("cannot end non multi ops")
|
|
}
|
|
o.multipOp = false
|
|
}
|
|
|
|
func WriteMulti(o *Ops, n int) []byte {
|
|
if !o.multipOp {
|
|
panic("cannot use multi ops in single ops")
|
|
}
|
|
o.data = append(o.data, make([]byte, n)...)
|
|
return o.data[len(o.data)-n:]
|
|
}
|
|
|
|
func PushMacro(o *Ops) StackID {
|
|
return o.macroStack.push()
|
|
}
|
|
|
|
func PopMacro(o *Ops, id StackID) {
|
|
o.macroStack.pop(id)
|
|
}
|
|
|
|
func FillMacro(o *Ops, startPC PC) {
|
|
pc := PCFor(o)
|
|
// Fill out the macro definition reserved in Record.
|
|
data := o.data[startPC.data:]
|
|
data = data[:TypeMacroLen]
|
|
data[0] = byte(TypeMacro)
|
|
bo := binary.LittleEndian
|
|
bo.PutUint32(data[1:], uint32(pc.data))
|
|
bo.PutUint32(data[5:], uint32(pc.refs))
|
|
}
|
|
|
|
func AddCall(o *Ops, callOps *Ops, pc PC, end PC) {
|
|
data := Write1(o, TypeCallLen, callOps)
|
|
data[0] = byte(TypeCall)
|
|
bo := binary.LittleEndian
|
|
bo.PutUint32(data[1:], uint32(pc.data))
|
|
bo.PutUint32(data[5:], uint32(pc.refs))
|
|
bo.PutUint32(data[9:], uint32(end.data))
|
|
bo.PutUint32(data[13:], uint32(end.refs))
|
|
}
|
|
|
|
func PushOp(o *Ops, kind StackKind) (StackID, uint32) {
|
|
return o.stacks[kind].push(), o.macroStack.currentID
|
|
}
|
|
|
|
func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
|
|
if o.macroStack.currentID != macroID {
|
|
panic("stack push and pop must not cross macro boundary")
|
|
}
|
|
o.stacks[kind].pop(sid)
|
|
}
|
|
|
|
func Write1(o *Ops, n int, ref1 interface{}) []byte {
|
|
o.data = append(o.data, make([]byte, n)...)
|
|
o.refs = append(o.refs, ref1)
|
|
return o.data[len(o.data)-n:]
|
|
}
|
|
|
|
func Write1String(o *Ops, n int, ref1 string) []byte {
|
|
o.data = append(o.data, make([]byte, n)...)
|
|
o.stringRefs = append(o.stringRefs, ref1)
|
|
o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1])
|
|
return o.data[len(o.data)-n:]
|
|
}
|
|
|
|
func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
|
|
o.data = append(o.data, make([]byte, n)...)
|
|
o.refs = append(o.refs, ref1, ref2)
|
|
return o.data[len(o.data)-n:]
|
|
}
|
|
|
|
func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
|
|
o.data = append(o.data, make([]byte, n)...)
|
|
o.stringRefs = append(o.stringRefs, ref2)
|
|
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
|
return o.data[len(o.data)-n:]
|
|
}
|
|
|
|
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
|
|
o.data = append(o.data, make([]byte, n)...)
|
|
o.refs = append(o.refs, ref1, ref2, ref3)
|
|
return o.data[len(o.data)-n:]
|
|
}
|
|
|
|
func PCFor(o *Ops) PC {
|
|
return PC{data: uint32(len(o.data)), refs: uint32(len(o.refs))}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Save the effective transformation.
|
|
func Save(o *Ops) StateOp {
|
|
o.nextStateID++
|
|
s := StateOp{
|
|
ops: o,
|
|
id: o.nextStateID,
|
|
macroID: o.macroStack.currentID,
|
|
}
|
|
bo := binary.LittleEndian
|
|
data := Write(o, TypeSaveLen)
|
|
data[0] = byte(TypeSave)
|
|
bo.PutUint32(data[1:], uint32(s.id))
|
|
return s
|
|
}
|
|
|
|
// Load a previously saved operations state given
|
|
// its ID.
|
|
func (s StateOp) Load() {
|
|
bo := binary.LittleEndian
|
|
data := Write(s.ops, TypeLoadLen)
|
|
data[0] = byte(TypeLoad)
|
|
bo.PutUint32(data[1:], uint32(s.id))
|
|
}
|
|
|
|
func DecodeCommand(d []byte) scene.Command {
|
|
var cmd scene.Command
|
|
copy(byteslice.Uint32(cmd[:]), d)
|
|
return cmd
|
|
}
|
|
|
|
func EncodeCommand(out []byte, cmd scene.Command) {
|
|
copy(out, byteslice.Uint32(cmd[:]))
|
|
}
|
|
|
|
func DecodeTransform(data []byte) (t f32.Affine2D, push bool) {
|
|
if OpType(data[0]) != TypeTransform {
|
|
panic("invalid op")
|
|
}
|
|
push = data[1] != 0
|
|
data = data[2:]
|
|
data = data[:4*6]
|
|
|
|
bo := binary.LittleEndian
|
|
a := math.Float32frombits(bo.Uint32(data))
|
|
b := math.Float32frombits(bo.Uint32(data[4*1:]))
|
|
c := math.Float32frombits(bo.Uint32(data[4*2:]))
|
|
d := math.Float32frombits(bo.Uint32(data[4*3:]))
|
|
e := math.Float32frombits(bo.Uint32(data[4*4:]))
|
|
f := math.Float32frombits(bo.Uint32(data[4*5:]))
|
|
return f32.NewAffine2D(a, b, c, d, e, f), push
|
|
}
|
|
|
|
func DecodeOpacity(data []byte) float32 {
|
|
if OpType(data[0]) != TypePushOpacity {
|
|
panic("invalid op")
|
|
}
|
|
bo := binary.LittleEndian
|
|
return math.Float32frombits(bo.Uint32(data[1:]))
|
|
}
|
|
|
|
// DecodeSave decodes the state id of a save op.
|
|
func DecodeSave(data []byte) int {
|
|
if OpType(data[0]) != TypeSave {
|
|
panic("invalid op")
|
|
}
|
|
bo := binary.LittleEndian
|
|
return int(bo.Uint32(data[1:]))
|
|
}
|
|
|
|
// DecodeLoad decodes the state id of a load op.
|
|
func DecodeLoad(data []byte) int {
|
|
if OpType(data[0]) != TypeLoad {
|
|
panic("invalid op")
|
|
}
|
|
bo := binary.LittleEndian
|
|
return int(bo.Uint32(data[1:]))
|
|
}
|
|
|
|
type opProp struct {
|
|
Size byte
|
|
NumRefs byte
|
|
}
|
|
|
|
var opProps = [0x100]opProp{
|
|
TypeMacro: {Size: TypeMacroLen, NumRefs: 0},
|
|
TypeCall: {Size: TypeCallLen, NumRefs: 1},
|
|
TypeDefer: {Size: TypeDeferLen, NumRefs: 0},
|
|
TypeTransform: {Size: TypeTransformLen, NumRefs: 0},
|
|
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
|
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
|
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
|
TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0},
|
|
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
|
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
|
TypeColor: {Size: TypeColorLen, NumRefs: 0},
|
|
TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
|
|
TypePass: {Size: TypePassLen, NumRefs: 0},
|
|
TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
|
|
TypeInput: {Size: TypeInputLen, NumRefs: 1},
|
|
TypeKeyInput: {Size: TypeKeyInputLen, NumRefs: 2},
|
|
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
|
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
|
TypeAux: {Size: TypeAuxLen, NumRefs: 0},
|
|
TypeClip: {Size: TypeClipLen, NumRefs: 0},
|
|
TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
|
|
TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
|
|
TypePath: {Size: TypePathLen, NumRefs: 0},
|
|
TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
|
|
TypeSemanticLabel: {Size: TypeSemanticLabelLen, NumRefs: 1},
|
|
TypeSemanticDesc: {Size: TypeSemanticDescLen, NumRefs: 1},
|
|
TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
|
|
TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
|
|
TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
|
|
TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
|
|
}
|
|
|
|
func (t OpType) props() (size, numRefs uint32) {
|
|
v := opProps[t]
|
|
return uint32(v.Size), uint32(v.NumRefs)
|
|
}
|
|
|
|
func (t OpType) Size() uint32 {
|
|
return uint32(opProps[t].Size)
|
|
}
|
|
|
|
func (t OpType) NumRefs() uint32 {
|
|
return uint32(opProps[t].NumRefs)
|
|
}
|
|
|
|
func (t OpType) String() string {
|
|
switch t {
|
|
case TypeMacro:
|
|
return "Macro"
|
|
case TypeCall:
|
|
return "Call"
|
|
case TypeDefer:
|
|
return "Defer"
|
|
case TypeTransform:
|
|
return "Transform"
|
|
case TypePopTransform:
|
|
return "PopTransform"
|
|
case TypePushOpacity:
|
|
return "PushOpacity"
|
|
case TypePopOpacity:
|
|
return "PopOpacity"
|
|
case TypeInvalidate:
|
|
return "Invalidate"
|
|
case TypeImage:
|
|
return "Image"
|
|
case TypePaint:
|
|
return "Paint"
|
|
case TypeColor:
|
|
return "Color"
|
|
case TypeLinearGradient:
|
|
return "LinearGradient"
|
|
case TypePass:
|
|
return "Pass"
|
|
case TypePopPass:
|
|
return "PopPass"
|
|
case TypeInput:
|
|
return "Input"
|
|
case TypeKeyInput:
|
|
return "KeyInput"
|
|
case TypeSave:
|
|
return "Save"
|
|
case TypeLoad:
|
|
return "Load"
|
|
case TypeAux:
|
|
return "Aux"
|
|
case TypeClip:
|
|
return "Clip"
|
|
case TypePopClip:
|
|
return "PopClip"
|
|
case TypeCursor:
|
|
return "Cursor"
|
|
case TypePath:
|
|
return "Path"
|
|
case TypeStroke:
|
|
return "Stroke"
|
|
case TypeSemanticLabel:
|
|
return "SemanticDescription"
|
|
default:
|
|
panic("unknown OpType")
|
|
}
|
|
}
|