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) }