gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/widget/dnd.go (about)

     1  package widget
     2  
     3  import (
     4  	"io"
     5  
     6  	"gioui.org/f32"
     7  	"gioui.org/gesture"
     8  	"gioui.org/io/event"
     9  	"gioui.org/io/pointer"
    10  	"gioui.org/io/transfer"
    11  	"gioui.org/layout"
    12  	"gioui.org/op"
    13  	"gioui.org/op/clip"
    14  )
    15  
    16  // Draggable makes a widget draggable.
    17  type Draggable struct {
    18  	// Type contains the MIME type and matches transfer.SourceOp.
    19  	Type string
    20  
    21  	drag  gesture.Drag
    22  	click f32.Point
    23  	pos   f32.Point
    24  }
    25  
    26  func (d *Draggable) Layout(gtx layout.Context, w, drag layout.Widget) layout.Dimensions {
    27  	if !gtx.Enabled() {
    28  		return w(gtx)
    29  	}
    30  	dims := w(gtx)
    31  
    32  	stack := clip.Rect{Max: dims.Size}.Push(gtx.Ops)
    33  	d.drag.Add(gtx.Ops)
    34  	event.Op(gtx.Ops, d)
    35  	stack.Pop()
    36  
    37  	if drag != nil && d.drag.Pressed() {
    38  		rec := op.Record(gtx.Ops)
    39  		op.Offset(d.pos.Round()).Add(gtx.Ops)
    40  		drag(gtx)
    41  		op.Defer(gtx.Ops, rec.Stop())
    42  	}
    43  
    44  	return dims
    45  }
    46  
    47  // Dragging returns whether d is being dragged.
    48  func (d *Draggable) Dragging() bool {
    49  	return d.drag.Dragging()
    50  }
    51  
    52  // Update the draggable and returns the MIME type for which the Draggable was
    53  // requested to offer data, if any
    54  func (d *Draggable) Update(gtx layout.Context) (mime string, requested bool) {
    55  	pos := d.pos
    56  	for {
    57  		ev, ok := d.drag.Update(gtx.Metric, gtx.Source, gesture.Both)
    58  		if !ok {
    59  			break
    60  		}
    61  		switch ev.Kind {
    62  		case pointer.Press:
    63  			d.click = ev.Position
    64  			pos = f32.Point{}
    65  		case pointer.Drag, pointer.Release:
    66  			pos = ev.Position.Sub(d.click)
    67  		}
    68  	}
    69  	d.pos = pos
    70  
    71  	for {
    72  		e, ok := gtx.Event(transfer.SourceFilter{Target: d, Type: d.Type})
    73  		if !ok {
    74  			break
    75  		}
    76  		if e, ok := e.(transfer.RequestEvent); ok {
    77  			return e.Type, true
    78  		}
    79  	}
    80  	return "", false
    81  }
    82  
    83  // Offer the data ready for a drop. Must be called after being Requested.
    84  // The mime must be one in the requested list.
    85  func (d *Draggable) Offer(gtx layout.Context, mime string, data io.ReadCloser) {
    86  	gtx.Execute(transfer.OfferCmd{Tag: d, Type: mime, Data: data})
    87  }
    88  
    89  // Pos returns the drag position relative to its initial click position.
    90  func (d *Draggable) Pos() f32.Point {
    91  	return d.pos
    92  }