cchat-gtk/internal/ui/primitives/drag/drag.go

153 lines
3.9 KiB
Go

package drag
import (
"net/url"
"strings"
"github.com/diamondburned/cchat-gtk/internal/log"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
"github.com/pkg/errors"
)
func NewTargetEntry(target string, f gtk.TargetFlags, info uint) gtk.TargetEntry {
e, _ := gtk.TargetEntryNew(target, f, info)
return *e
}
// Find searches the given container for the draggable widget with the given
// name.
func Find(w primitives.Container, id string) int {
var index = -1 // not found default
primitives.EachChildren(w, func(i int, v interface{}) bool {
if primitives.GetName(v.(primitives.Namer)) == id {
index = i
return true
}
return false
})
return index
}
type MainDraggable interface {
ID() string
SetName(string)
SetSensitive(bool)
gtk.IWidget
Draggable
}
type Draggable interface {
DragSourceSet(gdk.ModifierType, []gtk.TargetEntry, gdk.DragAction)
DragDestSet(gtk.DestDefaults, []gtk.TargetEntry, gdk.DragAction)
primitives.Connector
}
func BindFileDest(dg Draggable, file func(path []string)) {
var dragEntries = []gtk.TargetEntry{
NewTargetEntry("text/uri-list", gtk.TARGET_OTHER_APP, 1),
}
dg.DragDestSet(gtk.DEST_DEFAULT_ALL, dragEntries, gdk.ACTION_COPY)
dg.Connect("drag-data-received",
func(_ gtk.IWidget, ctx *gdk.DragContext, x, y uint, data *gtk.SelectionData) {
// Get the files in form of line-delimited URIs
var uris = strings.Fields(string(data.GetData()))
// Create a path slice that we decode URIs into.
var paths = uris[:0]
// Decode the URIs.
for _, uri := range uris {
u, err := url.Parse(uri)
if err != nil {
log.Error(errors.Wrapf(err, "Failed parsing URI %q", uri))
continue
}
paths = append(paths, u.Path)
}
file(paths)
},
)
}
// Swapper is the type for a swap function.
type Swapper = func(targetID, movingID string)
// BindDraggable binds the draggable widget and make it drag-and-droppable. The
// parent MUST have its own state of children and MUST NOT rely on its container
// states.
//
// This function can take additional draggers, which will override the main
// draggable and will be the only widgets that can be dragged away. The source
// ID will be taken from the main draggable.
func BindDraggable(dg MainDraggable, icon string, fn Swapper, draggers ...Draggable) {
var atom = "data_" + icon
var dragAtom = gdk.GdkAtomIntern(atom, false)
var dragEntries = []gtk.TargetEntry{
NewTargetEntry(atom, gtk.TARGET_SAME_APP, 0),
}
// Set the ID for Find().
dg.SetName(dg.ID())
// Make closures function so we can use twice.
srcSet := func(dragger Draggable) {
// Drag source so you can drag the button away.
dragger.DragSourceSet(gdk.BUTTON1_MASK, dragEntries, gdk.ACTION_MOVE)
dragger.Connect("drag-data-get",
func(_ interface{}, ctx *gdk.DragContext, data *gtk.SelectionData) {
// Set the index-in-bytes.
data.SetData(dragAtom, []byte(dg.ID()))
},
)
dragger.Connect("drag-begin",
func(_ interface{}, ctx *gdk.DragContext) {
gtk.DragSetIconName(ctx, icon, 0, 0)
dg.SetSensitive(false)
},
)
dragger.Connect("drag-end",
func(interface{}) {
dg.SetSensitive(true)
},
)
}
dstSet := func(dragger Draggable) {
// Drag destination so you can drag the button here.
dragger.DragDestSet(gtk.DEST_DEFAULT_ALL, dragEntries, gdk.ACTION_MOVE)
dragger.Connect("drag-data-received",
func(_ gtk.IWidget, ctx *gdk.DragContext, x, y uint, data *gtk.SelectionData) {
// Receive the incoming row's ID and call MoveSession.
fn(dg.ID(), string(data.GetData()))
},
)
}
// If we have no extra draggers given, then the MainDraggable should also be
// a source.
if len(draggers) == 0 {
srcSet(dg)
} else {
// Else, set drag sources only on those extra draggables.
for _, dragger := range draggers {
srcSet(dragger)
dstSet(dragger)
}
}
// Make MainDraggable a drag destination as well.
dstSet(dg)
}