mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
io/input,io/transfer: [API] replace OfferOp with command
Also delete two tests that are no longer relevant. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -67,7 +67,6 @@ const (
|
|||||||
TypeClipboardWrite
|
TypeClipboardWrite
|
||||||
TypeSource
|
TypeSource
|
||||||
TypeTarget
|
TypeTarget
|
||||||
TypeOffer
|
|
||||||
TypeKeyInput
|
TypeKeyInput
|
||||||
TypeSave
|
TypeSave
|
||||||
TypeLoad
|
TypeLoad
|
||||||
@@ -148,7 +147,6 @@ const (
|
|||||||
TypeClipboardWriteLen = 1
|
TypeClipboardWriteLen = 1
|
||||||
TypeSourceLen = 1
|
TypeSourceLen = 1
|
||||||
TypeTargetLen = 1
|
TypeTargetLen = 1
|
||||||
TypeOfferLen = 1
|
|
||||||
TypeKeyInputLen = 1 + 1
|
TypeKeyInputLen = 1 + 1
|
||||||
TypeSaveLen = 1 + 4
|
TypeSaveLen = 1 + 4
|
||||||
TypeLoadLen = 1 + 4
|
TypeLoadLen = 1 + 4
|
||||||
@@ -427,7 +425,6 @@ var opProps = [0x100]opProp{
|
|||||||
TypeClipboardWrite: {Size: TypeClipboardWriteLen, NumRefs: 1},
|
TypeClipboardWrite: {Size: TypeClipboardWriteLen, NumRefs: 1},
|
||||||
TypeSource: {Size: TypeSourceLen, NumRefs: 2},
|
TypeSource: {Size: TypeSourceLen, NumRefs: 2},
|
||||||
TypeTarget: {Size: TypeTargetLen, NumRefs: 2},
|
TypeTarget: {Size: TypeTargetLen, NumRefs: 2},
|
||||||
TypeOffer: {Size: TypeOfferLen, NumRefs: 3},
|
|
||||||
TypeKeyInput: {Size: TypeKeyInputLen, NumRefs: 2},
|
TypeKeyInput: {Size: TypeKeyInputLen, NumRefs: 2},
|
||||||
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
||||||
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
||||||
@@ -498,8 +495,6 @@ func (t OpType) String() string {
|
|||||||
return "Source"
|
return "Source"
|
||||||
case TypeTarget:
|
case TypeTarget:
|
||||||
return "Target"
|
return "Target"
|
||||||
case TypeOffer:
|
|
||||||
return "Offer"
|
|
||||||
case TypeKeyInput:
|
case TypeKeyInput:
|
||||||
return "KeyInput"
|
return "KeyInput"
|
||||||
case TypeSave:
|
case TypeSave:
|
||||||
|
|||||||
+35
-41
@@ -72,8 +72,6 @@ type pointerHandler struct {
|
|||||||
|
|
||||||
sourceMimes []string
|
sourceMimes []string
|
||||||
targetMimes []string
|
targetMimes []string
|
||||||
offeredMime string
|
|
||||||
data io.ReadCloser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type areaOp struct {
|
type areaOp struct {
|
||||||
@@ -236,16 +234,23 @@ func (c *pointerCollector) newHandler(tag event.Tag, events *handlerEvents) *poi
|
|||||||
tag: tag,
|
tag: tag,
|
||||||
pass: c.state.pass > 0,
|
pass: c.state.pass > 0,
|
||||||
})
|
})
|
||||||
h, ok := c.q.handlers[tag]
|
h := c.q.handlerFor(tag, events)
|
||||||
|
h.area = areaID
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *pointerQueue) handlerFor(tag event.Tag, events *handlerEvents) *pointerHandler {
|
||||||
|
h, ok := q.handlers[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
h = new(pointerHandler)
|
h = &pointerHandler{
|
||||||
c.q.handlers[tag] = h
|
area: -1,
|
||||||
|
}
|
||||||
|
q.handlers[tag] = h
|
||||||
// Cancel handlers on (each) first appearance, but don't
|
// Cancel handlers on (each) first appearance, but don't
|
||||||
// trigger redraw.
|
// trigger redraw.
|
||||||
events.AddNoRedraw(tag, pointer.Event{Kind: pointer.Cancel})
|
events.AddNoRedraw(tag, pointer.Event{Kind: pointer.Cancel})
|
||||||
}
|
}
|
||||||
h.active = true
|
h.active = true
|
||||||
h.area = areaID
|
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,10 +337,27 @@ func (c *pointerCollector) targetOp(op transfer.TargetOp, events *handlerEvents)
|
|||||||
h.targetMimes = append(h.targetMimes, op.Type)
|
h.targetMimes = append(h.targetMimes, op.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *pointerCollector) offerOp(op transfer.OfferOp, events *handlerEvents) {
|
func (q *pointerQueue) offerData(req transfer.OfferCmd, events *handlerEvents) {
|
||||||
h := c.newHandler(op.Tag, events)
|
transferIdx := len(q.transfers)
|
||||||
h.offeredMime = op.Type
|
q.transfers = append(q.transfers, req.Data)
|
||||||
h.data = op.Data
|
for i := range q.pointers {
|
||||||
|
p := &q.pointers[i]
|
||||||
|
if p.dataSource != req.Tag {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer q.deliverTransferCancelEvent(p, events)
|
||||||
|
if p.dataTarget == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
events.Add(p.dataTarget, transfer.DataEvent{
|
||||||
|
Type: req.Type,
|
||||||
|
Open: func() io.ReadCloser {
|
||||||
|
q.transfers[transferIdx] = nil
|
||||||
|
return req.Data
|
||||||
|
},
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *pointerCollector) reset() {
|
func (c *pointerCollector) reset() {
|
||||||
@@ -537,6 +559,7 @@ func (q *pointerQueue) reset() {
|
|||||||
for _, h := range q.handlers {
|
for _, h := range q.handlers {
|
||||||
// Reset handler.
|
// Reset handler.
|
||||||
h.active = false
|
h.active = false
|
||||||
|
h.area = -1
|
||||||
h.wantsGrab = false
|
h.wantsGrab = false
|
||||||
h.types = 0
|
h.types = 0
|
||||||
h.sourceMimes = h.sourceMimes[:0]
|
h.sourceMimes = h.sourceMimes[:0]
|
||||||
@@ -596,7 +619,6 @@ func (q *pointerQueue) Frame(events *handlerEvents) {
|
|||||||
for i := range q.pointers {
|
for i := range q.pointers {
|
||||||
p := &q.pointers[i]
|
p := &q.pointers[i]
|
||||||
q.deliverEnterLeaveEvents(p, events, p.last)
|
q.deliverEnterLeaveEvents(p, events, p.last)
|
||||||
q.deliverTransferDataEvent(p, events)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,10 +812,10 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEv
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h := q.handlers[k]
|
h := q.handlers[k]
|
||||||
|
e := e
|
||||||
e.Kind = pointer.Leave
|
e.Kind = pointer.Leave
|
||||||
|
|
||||||
if e.Kind&h.types != 0 {
|
if e.Kind&h.types != 0 {
|
||||||
e := e
|
|
||||||
e.Position = q.invTransform(h.area, e.Position)
|
e.Position = q.invTransform(h.area, e.Position)
|
||||||
events.Add(k, e)
|
events.Add(k, e)
|
||||||
}
|
}
|
||||||
@@ -804,10 +826,10 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEv
|
|||||||
if _, found := searchTag(p.entered, k); found {
|
if _, found := searchTag(p.entered, k); found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
e := e
|
||||||
e.Kind = pointer.Enter
|
e.Kind = pointer.Enter
|
||||||
|
|
||||||
if e.Kind&h.types != 0 {
|
if e.Kind&h.types != 0 {
|
||||||
e := e
|
|
||||||
e.Position = q.invTransform(h.area, e.Position)
|
e.Position = q.invTransform(h.area, e.Position)
|
||||||
events.Add(k, e)
|
events.Add(k, e)
|
||||||
}
|
}
|
||||||
@@ -855,32 +877,6 @@ func (q *pointerQueue) deliverDropEvent(p *pointerInfo, events *handlerEvents) {
|
|||||||
q.deliverTransferCancelEvent(p, events)
|
q.deliverTransferCancelEvent(p, events)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *pointerQueue) deliverTransferDataEvent(p *pointerInfo, events *handlerEvents) {
|
|
||||||
if p.dataSource == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
src := q.handlers[p.dataSource]
|
|
||||||
if src.data == nil {
|
|
||||||
// Data not received yet.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.dataTarget == nil {
|
|
||||||
q.deliverTransferCancelEvent(p, events)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Send the offered data to the target.
|
|
||||||
transferIdx := len(q.transfers)
|
|
||||||
events.Add(p.dataTarget, transfer.DataEvent{
|
|
||||||
Type: src.offeredMime,
|
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
q.transfers[transferIdx] = nil
|
|
||||||
return src.data
|
|
||||||
},
|
|
||||||
})
|
|
||||||
q.transfers = append(q.transfers, src.data)
|
|
||||||
p.dataTarget = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *pointerQueue) deliverTransferCancelEvent(p *pointerInfo, events *handlerEvents) {
|
func (q *pointerQueue) deliverTransferCancelEvent(p *pointerInfo, events *handlerEvents) {
|
||||||
events.Add(p.dataSource, transfer.CancelEvent{})
|
events.Add(p.dataSource, transfer.CancelEvent{})
|
||||||
// Cancel all potential targets.
|
// Cancel all potential targets.
|
||||||
@@ -890,8 +886,6 @@ func (q *pointerQueue) deliverTransferCancelEvent(p *pointerInfo, events *handle
|
|||||||
events.Add(k, transfer.CancelEvent{})
|
events.Add(k, transfer.CancelEvent{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
src.offeredMime = ""
|
|
||||||
src.data = nil
|
|
||||||
p.dataSource = nil
|
p.dataSource = nil
|
||||||
p.dataTarget = nil
|
p.dataTarget = nil
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-128
@@ -821,15 +821,6 @@ func TestTransfer(t *testing.T) {
|
|||||||
// Cancel is received when the pointer is first seen.
|
// Cancel is received when the pointer is first seen.
|
||||||
cancel := pointer.Event{Kind: pointer.Cancel}
|
cancel := pointer.Event{Kind: pointer.Cancel}
|
||||||
|
|
||||||
t.Run("transfer.Offer should panic on nil Data", func(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if recover() == nil {
|
|
||||||
t.Error("expected panic upon invalid data")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
transfer.OfferOp{}.Add(new(op.Ops))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("drop on no target", func(t *testing.T) {
|
t.Run("drop on no target", func(t *testing.T) {
|
||||||
ops := new(op.Ops)
|
ops := new(op.Ops)
|
||||||
src, tgt := setup(ops, "file", "file")
|
src, tgt := setup(ops, "file", "file")
|
||||||
@@ -959,16 +950,13 @@ func TestTransfer(t *testing.T) {
|
|||||||
|
|
||||||
// Offer valid type and data.
|
// Offer valid type and data.
|
||||||
ofr := &offer{data: "hello"}
|
ofr := &offer{data: "hello"}
|
||||||
transfer.OfferOp{
|
r.Source().Queue(transfer.OfferCmd{Tag: src, Type: "file", Data: ofr})
|
||||||
Tag: src,
|
|
||||||
Type: "file",
|
|
||||||
Data: ofr,
|
|
||||||
}.Add(ops)
|
|
||||||
r.Frame(ops)
|
r.Frame(ops)
|
||||||
evs := r.Events(tgt)
|
evs := r.Events(tgt)
|
||||||
if len(evs) != 1 {
|
if len(evs) != 2 {
|
||||||
t.Fatalf("unexpected number of events: %d, want 1", len(evs))
|
t.Fatalf("unexpected number of events: %d, want 2", len(evs))
|
||||||
}
|
}
|
||||||
|
assertEventSequence(t, evs[1:], transfer.CancelEvent{})
|
||||||
dataEvent, ok := evs[0].(transfer.DataEvent)
|
dataEvent, ok := evs[0].(transfer.DataEvent)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("unexpected event type: %T, want %T", dataEvent, transfer.DataEvent{})
|
t.Fatalf("unexpected event type: %T, want %T", dataEvent, transfer.DataEvent{})
|
||||||
@@ -984,9 +972,8 @@ func TestTransfer(t *testing.T) {
|
|||||||
if ofr.closed {
|
if ofr.closed {
|
||||||
t.Error("offer closed prematurely")
|
t.Error("offer closed prematurely")
|
||||||
}
|
}
|
||||||
r.Frame(ops)
|
|
||||||
assertEventSequence(t, r.Events(src), transfer.CancelEvent{})
|
assertEventSequence(t, r.Events(src), transfer.CancelEvent{})
|
||||||
assertEventSequence(t, r.Events(tgt), transfer.CancelEvent{})
|
r.Frame(ops)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("drop on valid target, DataEvent not used", func(t *testing.T) {
|
t.Run("drop on valid target, DataEvent not used", func(t *testing.T) {
|
||||||
@@ -1017,122 +1004,16 @@ func TestTransfer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
ofr := &offer{data: "hello"}
|
ofr := &offer{data: "hello"}
|
||||||
transfer.OfferOp{
|
r.Source().Queue(transfer.OfferCmd{Tag: src, Type: "file", Data: ofr})
|
||||||
Tag: src,
|
|
||||||
Type: "file",
|
|
||||||
Data: ofr,
|
|
||||||
}.Add(ops)
|
|
||||||
r.Frame(ops)
|
|
||||||
// DataEvent should be used here. The next frame should close it as unused.
|
|
||||||
r.Frame(ops)
|
r.Frame(ops)
|
||||||
assertEventSequence(t, r.Events(src), transfer.CancelEvent{})
|
assertEventSequence(t, r.Events(src), transfer.CancelEvent{})
|
||||||
assertEventSequence(t, r.Events(tgt), transfer.CancelEvent{})
|
// Ignore DataEvent and verify that the next frame closes it as unused.
|
||||||
|
assertEventSequence(t, r.Events(tgt)[1:], transfer.CancelEvent{})
|
||||||
|
r.Frame(ops)
|
||||||
if !ofr.closed {
|
if !ofr.closed {
|
||||||
t.Error("offer was not closed")
|
t.Error("offer was not closed")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("valid target enter/leave events", func(t *testing.T) {
|
|
||||||
ops := new(op.Ops)
|
|
||||||
src, _ := setup(ops, "file", "file")
|
|
||||||
pass := pointer.PassOp{}.Push(ops)
|
|
||||||
stack := clip.Rect(tgtArea).Push(ops)
|
|
||||||
tag := new(int)
|
|
||||||
pointer.InputOp{
|
|
||||||
Tag: tag,
|
|
||||||
Kinds: pointer.Enter | pointer.Leave,
|
|
||||||
}.Add(ops)
|
|
||||||
stack.Pop()
|
|
||||||
pass.Pop()
|
|
||||||
|
|
||||||
var r Router
|
|
||||||
r.Frame(ops)
|
|
||||||
// Drag.
|
|
||||||
r.Queue(
|
|
||||||
pointer.Event{
|
|
||||||
Position: f32.Pt(10, 10),
|
|
||||||
Kind: pointer.Press,
|
|
||||||
},
|
|
||||||
pointer.Event{
|
|
||||||
Position: f32.Pt(10, 10),
|
|
||||||
Kind: pointer.Move,
|
|
||||||
},
|
|
||||||
pointer.Event{
|
|
||||||
Position: f32.Pt(40, 10),
|
|
||||||
Kind: pointer.Move,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assertEventPointerTypeSequence(t, r.Events(tag), pointer.Cancel, pointer.Enter)
|
|
||||||
|
|
||||||
// Drop.
|
|
||||||
r.Queue(
|
|
||||||
pointer.Event{
|
|
||||||
Position: f32.Pt(40, 10),
|
|
||||||
Kind: pointer.Release,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Offer valid type and data.
|
|
||||||
ofr := &offer{data: "hello"}
|
|
||||||
transfer.OfferOp{
|
|
||||||
Tag: src,
|
|
||||||
Type: "file",
|
|
||||||
Data: ofr,
|
|
||||||
}.Add(ops)
|
|
||||||
r.Frame(ops)
|
|
||||||
assertEventPointerTypeSequence(t, r.Events(tag), pointer.Leave)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("invalid target NO enter/leave events", func(t *testing.T) {
|
|
||||||
ops := new(op.Ops)
|
|
||||||
src, _ := setup(ops, "file", "nofile")
|
|
||||||
pass := pointer.PassOp{}.Push(ops)
|
|
||||||
stack := clip.Rect(tgtArea).Push(ops)
|
|
||||||
tag := new(int)
|
|
||||||
pointer.InputOp{
|
|
||||||
Tag: tag,
|
|
||||||
Kinds: pointer.Enter | pointer.Leave,
|
|
||||||
}.Add(ops)
|
|
||||||
stack.Pop()
|
|
||||||
pass.Pop()
|
|
||||||
|
|
||||||
var r Router
|
|
||||||
r.Frame(ops)
|
|
||||||
// Drag.
|
|
||||||
r.Queue(
|
|
||||||
pointer.Event{
|
|
||||||
Position: f32.Pt(10, 10),
|
|
||||||
Kind: pointer.Press,
|
|
||||||
},
|
|
||||||
pointer.Event{
|
|
||||||
Position: f32.Pt(10, 10),
|
|
||||||
Kind: pointer.Move,
|
|
||||||
},
|
|
||||||
pointer.Event{
|
|
||||||
Position: f32.Pt(40, 10),
|
|
||||||
Kind: pointer.Move,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assertEventPointerTypeSequence(t, r.Events(tag), pointer.Cancel)
|
|
||||||
|
|
||||||
// Drop.
|
|
||||||
r.Queue(
|
|
||||||
pointer.Event{
|
|
||||||
Position: f32.Pt(40, 10),
|
|
||||||
Kind: pointer.Release,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Offer valid type and data.
|
|
||||||
ofr := &offer{data: "hello"}
|
|
||||||
transfer.OfferOp{
|
|
||||||
Tag: src,
|
|
||||||
Type: "file",
|
|
||||||
Data: ofr,
|
|
||||||
}.Add(ops)
|
|
||||||
r.Frame(ops)
|
|
||||||
assertEventPointerTypeSequence(t, r.Events(tag), pointer.Leave)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeferredInputOp(t *testing.T) {
|
func TestDeferredInputOp(t *testing.T) {
|
||||||
|
|||||||
+2
-8
@@ -5,7 +5,6 @@ package input
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -221,6 +220,8 @@ func (q *Router) executeCommands() {
|
|||||||
q.key.queue.softKeyboard(req.Show)
|
q.key.queue.softKeyboard(req.Show)
|
||||||
case key.SnippetCmd:
|
case key.SnippetCmd:
|
||||||
q.key.queue.setSnippet(req)
|
q.key.queue.setSnippet(req)
|
||||||
|
case transfer.OfferCmd:
|
||||||
|
q.pointer.queue.offerData(req, &q.handlers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
q.commands = nil
|
q.commands = nil
|
||||||
@@ -498,13 +499,6 @@ func (q *Router) collect() {
|
|||||||
Type: encOp.Refs[1].(string),
|
Type: encOp.Refs[1].(string),
|
||||||
}
|
}
|
||||||
pc.targetOp(op, &q.handlers)
|
pc.targetOp(op, &q.handlers)
|
||||||
case ops.TypeOffer:
|
|
||||||
op := transfer.OfferOp{
|
|
||||||
Tag: encOp.Refs[0].(event.Tag),
|
|
||||||
Type: encOp.Refs[1].(string),
|
|
||||||
Data: encOp.Refs[2].(io.ReadCloser),
|
|
||||||
}
|
|
||||||
pc.offerOp(op, &q.handlers)
|
|
||||||
case ops.TypeActionInput:
|
case ops.TypeActionInput:
|
||||||
act := system.Action(encOp.Data[1])
|
act := system.Action(encOp.Data[1])
|
||||||
pc.actionInputOp(act)
|
pc.actionInputOp(act)
|
||||||
|
|||||||
+21
-29
@@ -2,11 +2,11 @@
|
|||||||
//
|
//
|
||||||
// The transfer protocol is as follows:
|
// The transfer protocol is as follows:
|
||||||
//
|
//
|
||||||
// - Data sources are registered with SourceOps, data targets with TargetOps.
|
// - Data sources use [SourceFilter] to receive [InitiateEvent]s when a drag
|
||||||
// - A data source receives a RequestEvent when a transfer is initiated.
|
// is initiated, and [RequestEvent]s for each initiation of a data transfer.
|
||||||
// It must respond with an OfferOp.
|
// Sources respond to requests with [OfferCommand].
|
||||||
// - The target receives a DataEvent when transferring to it. It must close
|
// - Data targets use [TargetFilter] to receive [DataEvent]s for receiving data.
|
||||||
// the event data after use.
|
// The target must close the data event after use.
|
||||||
//
|
//
|
||||||
// When a user initiates a pointer-guided drag and drop transfer, the
|
// When a user initiates a pointer-guided drag and drop transfer, the
|
||||||
// source as well as all potential targets receive an InitiateEvent.
|
// source as well as all potential targets receive an InitiateEvent.
|
||||||
@@ -25,6 +25,21 @@ import (
|
|||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OfferCmd is used by data sources as a response to a RequestEvent.
|
||||||
|
type OfferCmd struct {
|
||||||
|
Tag event.Tag
|
||||||
|
// Type is the MIME type of Data.
|
||||||
|
// It must be the Type from the corresponding RequestEvent.
|
||||||
|
Type string
|
||||||
|
// Data contains the offered data. It is closed when the
|
||||||
|
// transfer is complete or cancelled.
|
||||||
|
// Data must be kept valid until closed, and it may be used from
|
||||||
|
// a goroutine separate from the one processing the frame..
|
||||||
|
Data io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OfferCmd) ImplementsCommand() {}
|
||||||
|
|
||||||
// SourceOp registers a tag as a data source for a MIME type.
|
// SourceOp registers a tag as a data source for a MIME type.
|
||||||
// Use multiple SourceOps if a tag supports multiple types.
|
// Use multiple SourceOps if a tag supports multiple types.
|
||||||
type SourceOp struct {
|
type SourceOp struct {
|
||||||
@@ -41,19 +56,6 @@ type TargetOp struct {
|
|||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
// OfferOp is used by data sources as a response to a RequestEvent.
|
|
||||||
type OfferOp struct {
|
|
||||||
Tag event.Tag
|
|
||||||
// Type is the MIME type of Data.
|
|
||||||
// It must be the Type from the corresponding RequestEvent.
|
|
||||||
Type string
|
|
||||||
// Data contains the offered data. It is closed when the
|
|
||||||
// transfer is complete or cancelled.
|
|
||||||
// Data must be kept valid until closed, and it may be used from
|
|
||||||
// a goroutine separate from the one processing the frame..
|
|
||||||
Data io.ReadCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op SourceOp) Add(o *op.Ops) {
|
func (op SourceOp) Add(o *op.Ops) {
|
||||||
data := ops.Write2(&o.Internal, ops.TypeSourceLen, op.Tag, op.Type)
|
data := ops.Write2(&o.Internal, ops.TypeSourceLen, op.Tag, op.Type)
|
||||||
data[0] = byte(ops.TypeSource)
|
data[0] = byte(ops.TypeSource)
|
||||||
@@ -64,18 +66,8 @@ func (op TargetOp) Add(o *op.Ops) {
|
|||||||
data[0] = byte(ops.TypeTarget)
|
data[0] = byte(ops.TypeTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the offer to the list of operations.
|
|
||||||
// It panics if the Data field is not set.
|
|
||||||
func (op OfferOp) Add(o *op.Ops) {
|
|
||||||
if op.Data == nil {
|
|
||||||
panic("invalid nil data in OfferOp")
|
|
||||||
}
|
|
||||||
data := ops.Write3(&o.Internal, ops.TypeOfferLen, op.Tag, op.Type, op.Data)
|
|
||||||
data[0] = byte(ops.TypeOffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestEvent requests data from a data source. The source must
|
// RequestEvent requests data from a data source. The source must
|
||||||
// respond with an OfferOp.
|
// respond with an OfferCmd.
|
||||||
type RequestEvent struct {
|
type RequestEvent struct {
|
||||||
// Type is the first matched type between the source and the target.
|
// Type is the first matched type between the source and the target.
|
||||||
Type string
|
Type string
|
||||||
|
|||||||
+2
-6
@@ -77,12 +77,8 @@ func (d *Draggable) Update(gtx layout.Context) (mime string, requested bool) {
|
|||||||
|
|
||||||
// Offer the data ready for a drop. Must be called after being Requested.
|
// Offer the data ready for a drop. Must be called after being Requested.
|
||||||
// The mime must be one in the requested list.
|
// The mime must be one in the requested list.
|
||||||
func (d *Draggable) Offer(ops *op.Ops, mime string, data io.ReadCloser) {
|
func (d *Draggable) Offer(gtx layout.Context, mime string, data io.ReadCloser) {
|
||||||
transfer.OfferOp{
|
gtx.Queue(transfer.OfferCmd{Tag: &d.handle, Type: mime, Data: data})
|
||||||
Tag: &d.handle,
|
|
||||||
Type: mime,
|
|
||||||
Data: data,
|
|
||||||
}.Add(ops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pos returns the drag position relative to its initial click position.
|
// Pos returns the drag position relative to its initial click position.
|
||||||
|
|||||||
+3
-3
@@ -51,12 +51,12 @@ func TestDraggable(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
ofr := &offer{data: "hello"}
|
ofr := &offer{data: "hello"}
|
||||||
drag.Offer(gtx.Ops, "file", ofr)
|
drag.Offer(gtx, "file", ofr)
|
||||||
r.Frame(gtx.Ops)
|
r.Frame(gtx.Ops)
|
||||||
|
|
||||||
evs := r.Events(drag)
|
evs := r.Events(drag)
|
||||||
if len(evs) != 1 {
|
if len(evs) != 2 {
|
||||||
t.Fatalf("expected 1 event, got %d", len(evs))
|
t.Fatalf("expected 2 event, got %d", len(evs))
|
||||||
}
|
}
|
||||||
ev := evs[0].(transfer.DataEvent)
|
ev := evs[0].(transfer.DataEvent)
|
||||||
ev.Open = nil
|
ev.Open = nil
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func ExampleDraggable_Layout() {
|
|||||||
// drag must respond with an Offer event when requested.
|
// drag must respond with an Offer event when requested.
|
||||||
// Use the drag method for this.
|
// Use the drag method for this.
|
||||||
if m, ok := drag.Update(gtx); ok {
|
if m, ok := drag.Update(gtx); ok {
|
||||||
drag.Offer(gtx.Ops, m, offer{Data: "hello world"})
|
drag.Offer(gtx, m, offer{Data: "hello world"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the area for drops.
|
// Setup the area for drops.
|
||||||
|
|||||||
Reference in New Issue
Block a user