Skip to content

Conversation

@dweymouth
Copy link
Contributor

@dweymouth dweymouth commented Jul 15, 2025

Description:

RfC / Draft of API to allow drag and drop between CanvasObjects

For #142

Example use cases

Dragging to reorder items in a list (with possible multi-selection)

  • The list item widget (each individual row) implements DragSource
  • When a selected list item row is dragged, it returns one DragItem for each selected row
  • The list itself (extended) implements DropTarget, and as DropDragged is invoked, it draws a horizontal line to indicate where the items would be inserted if they were to be dropped
  • Custom mime types and URI schemes can be defined by the app to represent to itself the different types of content it supports drag-and-drop for

Dragging DocTabs items from one DocTabs widget to another (eg multi-pane text editor)

  • The tab itself can implement DragSource
  • When dragged over a potential DropTarget (e.g. another DocTabs) the tab item can gray itself out in response to DropPending
  • The DocTabs implements DropTarget and communicates with the tabitems renderer to render a horizontal line in between adjacent tabs where the new document would be inserted.

Dragging items from one top-level Fyne window to another window in the same app (process)

  • The API should support this, just need to handle it in the driver

Open questions

  • Should DropPending also pass the DropTarget as a parameter? I can see that it may be useful, for example, in a multi pane text editor app to know if a tab would be dropped in the same DocTabs (e.g. simply reordering the tabs) or a different DocTabs (moving from one pane to another)
  • How to handle drop from outside of the Fyne app onto a widget
  • How to handle drag from within a Fyne window and drop outside the window? (e.g. for browsers or text editors where dragging a tab outside the window opens a new window

Checklist:

  • Tests included.
  • Lint and formatter run with no errors.
  • Tests all pass.

Where applicable:

  • Public APIs match existing style and have Since: line.
  • Any breaking changes have a deprecation path or have been discussed.
  • Check for binary size increases when importing new modules.

@coveralls
Copy link

coveralls commented Jul 15, 2025

Coverage Status

coverage: 62.391%. remained the same
when pulling 38e6ba8 on dweymouth:feature/drag-and-drop
into 43c35cb on fyne-io:develop.

URI() URI
}

type DragItemCursor interface {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Cursor is a desktop extension, so a DragCursor should be too.
What might be good to handle at this level would be the drag preview which would appear next to the cursor and floating above the content, where desired?

// onto another CanvasObject which implements DropTarget.
type DragSource interface {
// DragSourceBegin is invoked when a drag and drop is initiated on this DragSource.
// It should return the DragItems it is sourcing, and optionally a non-nil DragItemCursor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not bake cursors into the main abstraction. Perhaps the DragItem could optionally implement DragItemCursor? Or even just Cursorable?


// DragSourceDragged is invoked when a drag and drop continues within this DragSource
// and has not yet entered over a potential DropTarget.
DragSourceDragged(*PointEvent)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this adds? Isn't it just the same as Hoverable except the widget has initiated a drag? (which it already knows)

// which MAY be a subset of the items returned by DragSourceBegin.
// Implementations may wish to use this hook to update rendering to
// highlight the item(s) which may be about to be dropped.
DropPending([]DragItem)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a concrete example of this in use? I'm not entirely sure that it will be supported at the OS level...

// that DropTarget, but the drag still remains in progress.
// Implementations may wish to cancel any visual changes made in response
// to the corresponding DropPending invocation.
DropUnpending()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the above is not needed then neither is this. Feels cleaner without - but I didn't quite understand the use-case

// accepted by a DropTarget.
DropCanceled()

// DropCanceled is invoked when a drag and drop ends with the content
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo, just FYI :)

// the in-progress drag and drop operation.
// If both the DragSource and this DropTarget return a cursor, the DropTarget
// cursor will be used to represent the drag and drop while it is dragged over this target.
DropBegin(*PointEvent, []DragItem) (accept []DragItem, cursor DragItemCursor)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above I don't think that cursor implementation is needed at this level - we already support that through Cursorable in the desktop extension.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to that I'm not sure that the implication in the comments that the cursor of the initialing drag would remain in use throughout the operation necessarily stands (normally).

// (resulting in a DropBegin invocation) continues to be dragged within this DropTarget.
// Implementations may wish to update rendering to show where the dragged content
// would land if dropped.
DropDragged(*PointEvent)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this and DropEnd needed? we already handle dragging in a non dnd operation, so if the position matters it can still be discovered...

DropEnd()

// Dropped is invoked when a drag and drop ends by being dropped on this DropTarget.
Dropped(*PointEvent)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the []DragItem more important than the *PointEvent?

Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all of this, a great bit of work towards fleshing it out.
I worry however that it is almost a super-set of DnD + cursor management + draggable, so have notes inline about how it could be trimmed down.

It looks like it could support the OS level integrations but the docs write very specifically about dragging to (or from) other CanvasObjects - but probably we don't want to limit to that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants