mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
03016f0c69
This patch adds internal Drag and Drop support to app.Windows. The new package io/transfer adds the ability to define draggable and droppable targets, which are leveraged by the new widget.Draggable type. The API is generic and could handle future use cases, such as external Drag and Drop. Updates gio#153 Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
474 lines
9.6 KiB
Go
474 lines
9.6 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 int
|
|
// data contains the serialized operations.
|
|
data []byte
|
|
// refs hold external references for operations.
|
|
refs []interface{}
|
|
// nextStateID is the id allocated for the next
|
|
// StateOp.
|
|
nextStateID int
|
|
|
|
macroStack stack
|
|
stacks [5]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
|
|
TypePushTransform
|
|
TypeTransform
|
|
TypePopTransform
|
|
TypeInvalidate
|
|
TypeImage
|
|
TypePaint
|
|
TypeColor
|
|
TypeLinearGradient
|
|
TypePass
|
|
TypePopPass
|
|
TypePointerInput
|
|
TypeClipboardRead
|
|
TypeClipboardWrite
|
|
TypeSource
|
|
TypeTarget
|
|
TypeOffer
|
|
TypeKeyInput
|
|
TypeKeyFocus
|
|
TypeKeySoftKeyboard
|
|
TypeSave
|
|
TypeLoad
|
|
TypeAux
|
|
TypeClip
|
|
TypePopClip
|
|
TypeProfile
|
|
TypeCursor
|
|
TypePath
|
|
TypeStroke
|
|
TypeSemanticLabel
|
|
TypeSemanticDesc
|
|
TypeSemanticClass
|
|
TypeSemanticSelected
|
|
TypeSemanticDisabled
|
|
)
|
|
|
|
type StackID struct {
|
|
id int
|
|
prev int
|
|
}
|
|
|
|
// StateOp represents a saved operation snapshop to be restored
|
|
// later.
|
|
type StateOp struct {
|
|
id int
|
|
macroID int
|
|
ops *Ops
|
|
}
|
|
|
|
// stack tracks the integer identities of stack operations to ensure correct
|
|
// pairing of their push and pop methods.
|
|
type stack struct {
|
|
currentID int
|
|
nextID int
|
|
}
|
|
|
|
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
|
|
MetaStack
|
|
)
|
|
|
|
const (
|
|
Path Shape = iota
|
|
Ellipse
|
|
Rect
|
|
)
|
|
|
|
const (
|
|
TypeMacroLen = 1 + 4 + 4
|
|
TypeCallLen = 1 + 4 + 4
|
|
TypeDeferLen = 1
|
|
TypePushTransformLen = 1 + 4*6
|
|
TypeTransformLen = 1 + 1 + 4*6
|
|
TypePopTransformLen = 1
|
|
TypeRedrawLen = 1 + 8
|
|
TypeImageLen = 1
|
|
TypePaintLen = 1
|
|
TypeColorLen = 1 + 4
|
|
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
|
TypePassLen = 1
|
|
TypePopPassLen = 1
|
|
TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4
|
|
TypeClipboardReadLen = 1
|
|
TypeClipboardWriteLen = 1
|
|
TypeSourceLen = 1
|
|
TypeTargetLen = 1
|
|
TypeOfferLen = 1
|
|
TypeKeyInputLen = 1 + 1
|
|
TypeKeyFocusLen = 1 + 1
|
|
TypeKeySoftKeyboardLen = 1 + 1
|
|
TypeSaveLen = 1 + 4
|
|
TypeLoadLen = 1 + 4
|
|
TypeAuxLen = 1
|
|
TypeClipLen = 1 + 4*4 + 1 + 1
|
|
TypePopClipLen = 1
|
|
TypeProfileLen = 1
|
|
TypeCursorLen = 1 + 1
|
|
TypePathLen = 8 + 1
|
|
TypeStrokeLen = 1 + 4
|
|
TypeSemanticLabelLen = 1
|
|
TypeSemanticDescLen = 1
|
|
TypeSemanticClassLen = 2
|
|
TypeSemanticSelectedLen = 2
|
|
TypeSemanticDisabledLen = 2
|
|
)
|
|
|
|
func (op *ClipOp) Decode(data []byte) {
|
|
if OpType(data[0]) != TypeClip {
|
|
panic("invalid op")
|
|
}
|
|
bo := binary.LittleEndian
|
|
r := image.Rectangle{
|
|
Min: image.Point{
|
|
X: int(int32(bo.Uint32(data[1:]))),
|
|
Y: int(int32(bo.Uint32(data[5:]))),
|
|
},
|
|
Max: image.Point{
|
|
X: int(int32(bo.Uint32(data[9:]))),
|
|
Y: int(int32(bo.Uint32(data[13:]))),
|
|
},
|
|
}
|
|
*op = ClipOp{
|
|
Bounds: r,
|
|
Outline: data[17] == 1,
|
|
Shape: Shape(data[18]),
|
|
}
|
|
}
|
|
|
|
func Reset(o *Ops) {
|
|
o.macroStack = stack{}
|
|
for i := range o.stacks {
|
|
o.stacks[i] = 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.nextStateID = 0
|
|
o.version++
|
|
}
|
|
|
|
func Write(o *Ops, n int) []byte {
|
|
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) {
|
|
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))
|
|
}
|
|
|
|
func PushOp(o *Ops, kind StackKind) (StackID, int) {
|
|
return o.stacks[kind].push(), o.macroStack.currentID
|
|
}
|
|
|
|
func PopOp(o *Ops, kind StackKind, sid StackID, macroID int) {
|
|
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 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 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: len(o.data), refs: 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
|
|
}
|
|
|
|
// 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:]))
|
|
}
|
|
|
|
func (t OpType) Size() int {
|
|
return [...]int{
|
|
TypeMacroLen,
|
|
TypeCallLen,
|
|
TypeDeferLen,
|
|
TypePushTransformLen,
|
|
TypeTransformLen,
|
|
TypePopTransformLen,
|
|
TypeRedrawLen,
|
|
TypeImageLen,
|
|
TypePaintLen,
|
|
TypeColorLen,
|
|
TypeLinearGradientLen,
|
|
TypePassLen,
|
|
TypePopPassLen,
|
|
TypePointerInputLen,
|
|
TypeClipboardReadLen,
|
|
TypeClipboardWriteLen,
|
|
TypeSourceLen,
|
|
TypeTargetLen,
|
|
TypeOfferLen,
|
|
TypeKeyInputLen,
|
|
TypeKeyFocusLen,
|
|
TypeKeySoftKeyboardLen,
|
|
TypeSaveLen,
|
|
TypeLoadLen,
|
|
TypeAuxLen,
|
|
TypeClipLen,
|
|
TypePopClipLen,
|
|
TypeProfileLen,
|
|
TypeCursorLen,
|
|
TypePathLen,
|
|
TypeStrokeLen,
|
|
TypeSemanticLabelLen,
|
|
TypeSemanticDescLen,
|
|
TypeSemanticClassLen,
|
|
TypeSemanticSelectedLen,
|
|
TypeSemanticDisabledLen,
|
|
}[t-firstOpIndex]
|
|
}
|
|
|
|
func (t OpType) NumRefs() int {
|
|
switch t {
|
|
case TypeKeyInput, TypeKeyFocus, TypePointerInput, TypeProfile, TypeCall, TypeClipboardRead, TypeClipboardWrite, TypeCursor, TypeSemanticLabel, TypeSemanticDesc:
|
|
return 1
|
|
case TypeImage, TypeSource, TypeTarget:
|
|
return 2
|
|
case TypeOffer:
|
|
return 3
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func (t OpType) String() string {
|
|
switch t {
|
|
case TypeMacro:
|
|
return "Macro"
|
|
case TypeCall:
|
|
return "Call"
|
|
case TypeDefer:
|
|
return "Defer"
|
|
case TypePushTransform:
|
|
return "PushTransform"
|
|
case TypeTransform:
|
|
return "Transform"
|
|
case TypePopTransform:
|
|
return "PopTransform"
|
|
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 TypePointerInput:
|
|
return "PointerInput"
|
|
case TypeClipboardRead:
|
|
return "ClipboardRead"
|
|
case TypeClipboardWrite:
|
|
return "ClipboardWrite"
|
|
case TypeSource:
|
|
return "Source"
|
|
case TypeTarget:
|
|
return "Target"
|
|
case TypeOffer:
|
|
return "Offer"
|
|
case TypeKeyInput:
|
|
return "KeyInput"
|
|
case TypeKeyFocus:
|
|
return "KeyFocus"
|
|
case TypeKeySoftKeyboard:
|
|
return "KeySoftKeyboard"
|
|
case TypeSave:
|
|
return "Save"
|
|
case TypeLoad:
|
|
return "Load"
|
|
case TypeAux:
|
|
return "Aux"
|
|
case TypeClip:
|
|
return "Clip"
|
|
case TypePopClip:
|
|
return "PopClip"
|
|
case TypeProfile:
|
|
return "Profile"
|
|
case TypeCursor:
|
|
return "Cursor"
|
|
case TypePath:
|
|
return "Path"
|
|
case TypeStroke:
|
|
return "Stroke"
|
|
case TypeSemanticLabel:
|
|
return "SemanticDescription"
|
|
default:
|
|
panic("unknown OpType")
|
|
}
|
|
}
|