Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Drag and Drop for X11 #1

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Drag and drop

Pugl is able to perform drag and drop, both as a source, and a target (including being both at once). This is done by using a number of callbacks. The following describes which callback is being called at what time of the DnD operation. In detail, the functions are explained in doxygen.

All mentioned positions are, if not explicitly stated, local to the Pugl window.

## Pugl is Source

- Pugl enables drag and drop if one key is being hold while the mouse is being dragged. `dndSourceKeyFunc` must return this key
- When the drag starts,
1. The status is set to `PuglDndSourceDragged`
2. `dndSourceDragFunc` is being called to inform Pugl about this and about where the drag occured.
- When the DnD target just changed (including if there was none previously), `PuglDndSourceOfferTypeFunc` is being called to get Pugl's offered mimetypes.
- As long as there is a target, `dndSourceActionFunc` is being called to find out the action Pugl wants to perform (copy, move or link, see PuglDndAction). The pugl implementation may (but there is no guarantee) not call this function if the target has asked Pugl not to send any position information inside a specified rectangle.
- After the source (pugl) has initiated the drop,
1. The status is set to `PuglDndSourceDropped
2. `dndSourceFinishedFunc` is being called to inform Pugl about whether the target has accepted the drop.
3. The status is reset to `PuglNotDndSource`
- In case the target requests any data (this can happen at any time after the drag, even during the drop), dndSourceProvideDataFunc is being called to ask Pugl to reveal the data for a specific mimetype.

## Pugl is Target

- When the pointer of the DnD source enters the Pugl window and a drag is active,
1. The status is set to `PuglDndTargetDragged`
2. `dndTargetOfferTypeFunc` is being called to let Pugl know which mimetypes the source offers
- Each time a pointer enters the Pugl window, or if it changes its position:
1. `dndTargetInformPositionFunc` tells Pugl where the mouse pointer is and what action the source wants to perform (copy, move or link, see PuglDndAction). Calls to this function may (but there is no guarantee) be suppressed if Pugl asked the source to suppress sending the pointer position in a specified rectangle.
2. `dndTargetChooseTypesToLookupFunc` lets Pugl choose which data (e.g. belonging to which mimetype) of the source it wants to read (this can also be done after the drop).
3. `dndTargetNoPositionInFunc` lets Pugl define a rectangle in which further position information shall not be offered by the source. This rectangle is optional, and even if specified, the source may ignore it.
- If the source has left the Pugl window without a drop, or if the drop has been canceled,
1. `dndTargetLeaveFunc` is being called
2. The status is reset to `PuglNotDndTarget`
- When a successful drop occurs on Pugl,
1. The status is set to `PuglDndSourceDropped`
2. `dndTargetDrop` is being called to inform Pugl about the drop.
3. `dndTargetChooseTypesToLookupFunc` lets Pugl again choose which data (e.g. belonging to which mimetype) of the source it wants to read.
4. `dndTargetAcceptDropFunc` is being called to let Pugl tell the source whether it accepts the drop or not. The function can also be used to do cleanups.
5. The status is reset to `PuglNotDndTarget`.
- Any time the source has sent the requested data, `dndTargetReceiveDataFunc` offers the data to Pugl.

## The DnD status

It can be crucial for apps to keep track of whether a dnd operation is in progress, and if yes in what state. E.g., many apps may want to react differently to mouse motion events if a dnd operation is in progress; some may even want to suppress motion events.

In order to not let the user write their own state machine, pugl has two status variables that are synched:
- `dnd_source_status` via on `dndSourceStatusFunc`
- `dnd_target_status` via on `dndTargetStatusFunc`

It is recommended to let these functions update variables in your handle. Debug output can also be helpful in these callbacks. Any other action should be done in the other dnd callbacks.

## Limitations

- The implementation is limited to X11 currently.
- If Pugl is the target, Pugl can call `dndTargetChooseTypesToLookupFunc` to lookup the data, but this can not be used to make a decision in `dndTargetAcceptDropFunc`.
264 changes: 263 additions & 1 deletion pugl/pugl.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,34 @@ typedef enum {
PUGL_KEY_SUPER
} PuglKey;

/**
Drag and Drop action types

These let dnd source announce how it wants to perform the DnD operation
*/
typedef enum {
//! Copying indicates that after a DnD, source and target will both
//! obtain the associated data, and both instances of the data are not
//! linked.
//! Example: Dragging a filename from a file browser and dropping it
//! over an UI to ask the UI to load the file. Afterwards, the
//! both have an instance of the file (the UI has the file
//! inside its RAM).
PuglDndActionCopy,
//! Moving is like copying, except that the associated data will be
//! removed from the source afterwards.
//! Example: Sorting effects in an audio app in order to change
//! their pipelining order
PuglDndActionMove,
//! Linking means that after the DnD operation, there will be a link
//! from the target to the associated data of the source. This is only
//! useful if source and target know a way to communicate to each other
//! Example: Dragging a knob from a synthesizer and dropping it on a
//! DAW's controller or automation pattern. Afterwards, the
//! knob will be changed according to the DAW.
PuglDndActionLink
} PuglDndAction;

/**
The type of a PuglEvent.
*/
Expand All @@ -163,7 +191,7 @@ typedef enum {
} PuglEventType;

typedef enum {
PUGL_IS_SEND_EVENT = 1
PUGL_IS_SEND_EVENT = 1 /**< True iff sent from another thread */
} PuglEventFlag;

/**
Expand All @@ -188,6 +216,13 @@ typedef struct {
Button press or release event.

For event types PUGL_BUTTON_PRESS and PUGL_BUTTON_RELEASE.

This event is *not* being suppressed during a drag-and-drop (dnd)
operation. Apps must be careful to handle this case correctly by looking
at TODO (a parameter to this function, e.g. making press an int and adding
a dnd mask for it? or a variable in PuglView?). For example, there may be
cases where moving the mouse should not change the widget's parameters
while a dnd operation is active.
*/
typedef struct {
PuglEventType type; /**< PUGL_BUTTON_PRESS or PUGL_BUTTON_RELEASE. */
Expand Down Expand Up @@ -363,6 +398,182 @@ typedef union {
PuglEventFocus focus; /**< PUGL_FOCUS_IN, PUGL_FOCUS_OUT. */
} PuglEvent;

typedef enum
{
//! pugl is not acting as a dnd source
PuglNotDndSource,
//! pugl is source, no dnd in progress, but any mouse click will
//! initiate a drag
PuglDndSourceReady,
//! pugl is source, drag done, but not dropped
PuglDndSourceDragged,
//! pugl is source, drop started, no acknowledgement from target yet
PuglDndSourceDropped,
} PuglDndSourceStatus;

typedef enum
{
//! pugl is not acting as a dnd target
PuglNotDndTarget,
//! pugl is target, a drag has been done on the source and the pointer
//! is over the pugl window
PuglDndTargetDragged,
//! pugl is target, the source has initiated the drop, pugl has not
//! yet replied
PuglDndTargetDropped
} PuglDndTargetStatus;

/*
event functions in old pugl-style
TODO: turn them into events, but there must be a way to still let the event
dispatch functions return values (as the old functions return values)
*/

//! Update the dnd status for the case that pugl may be a dnd source
typedef void (*PuglDndSourceStatusFunc)(PuglView* view,
PuglDndSourceStatus status);

/**
Let the source specify how, e.g. with what action, it wants to perform
the drag and drop.

@param rootx The pointer's x coordinates, global to the root window
@param rooty The pointer's y coordinates, global to the root window
*/
typedef PuglDndAction (*PuglDndSourceActionFunc)(PuglView* view,
int rootx, int rooty);

/**
Inform the source that a drag is initiated at (x, y) and let the source
accept or deny the drag

@param x The x coordinate, local to the pugl window
@param y The y coordinate, local to the pugl window
@return 1 iff the drag shall be accepted
*/
typedef int (*PuglDndSourceDragFunc)(PuglView* view, int x, int y);

/**
Inform the source that the drop has been finished
@param accepted whether the drop has been accepted or not
*/
typedef void (*PuglDndSourceFinishedFunc)(PuglView* view, int accepted);

/**
Return the key required to initiate DnD

While this key is being pressed, mouse movements will count as drags, and
the view.mouseFunc is not being called
@param accepted whether the drop has been accepted or not
*/
typedef PuglKey (*PuglDndSourceKeyFunc)(PuglView* view);

/**
Ask the source to return the property of the given type
@param rootx The pointer's x coordinates, global to the root window
@param rooty The pointer's y coordinates, global to the root window
@param slot Identifier specifying the type, or NULL
*/
typedef const char* (*PuglDndSourceOfferTypeFunc)(PuglView* view,
int rootx, int rooty,
int slot);

/**
Ask the source to write out a given property.
@param slot Identifier specifying the type
@param size Maximum size to write; must not be exceeded.
@param buffer An allocated buffer where the property must be written
@return The actual number of bytes filled from the buffer,
e.g. for a string str, strlen(str) + 1
*/
typedef int (*PuglDndSourceProvideDataFunc)(PuglView* view,
int slot, int size, char* buffer);

//! Update the dnd status for the case that pugl may be a dnd target
typedef void (*PuglDndTargetStatusFunc)(PuglView* view,
PuglDndTargetStatus status);

/**
Let the target say whether it accepts the previous drop.
@return A boolean specifying whether the drop was accepted or not.
*/
typedef int (*PuglDndTargetAcceptDropFunc)(PuglView* view);

/**
Let the target choose for which types it wants to lookup the data.

All requested types will be returned using dndTargetReceiveData.

@return A bitmask. The LSB corresponds to slot 0. Set it if you want to
lookup the property at slot 0.
*/
typedef int (*PuglDndTargetChooseTypesToLookupFunc)(PuglView* view);

/**
Inform the target that the user dropped the mouse.

dndTargetChooseTypesToLookup() will be called once after this.
*/
typedef void (*PuglDndTargetDropFunc)(PuglView* view);

/**
Inform the target of a new position and the action.

This is being sent when the mouse has been moved while being inside the pugl
window. It may be suppressed if the PuglDndTargetNoPositionInFunc has
been used.

@param x x coordinate, local to the pugl window
@param y y coordinate, local to the pugl window
@param action The action the source wants to perform
@return whether a drop with this action would be possible here
*/
typedef int (*PuglDndTargetInformPositionFunc)(PuglView* view, int x, int y,
PuglDndAction action);

/**
Inform the target that the mouse pointer is not over it anymore
*/
typedef void (*PuglDndTargetLeaveFunc)(PuglView* view);

/**
Lets the target define a rectangle in which no
new position events shall be sent.

The target must fill in the values for all arguments or return zero.

@param x x-coordinate, local to the pugl window
@param y y-coordinate, local to the pugl window
@param w width
@param h height
@return zero iff no such rectangle shall be defined
*/
typedef int (*PuglDndTargetNoPositionInFunc)(PuglView* view,
int* x, int* y, int* w, int *h);

/**
Inform the target that the source offers a mimetype
@param slot The slot which the mimetypes has been assigned to
@param mimetype Mimetype string, like e.g. "text/plain". On X11,
guaranteed to be null terminated. The target can not rely on it
existing after the function is left.
*/
typedef void (*PuglDndTargetOfferTypeFunc)(PuglView* view,
int slot, const char* mimetype);

/**
Retreive a property.
@param slot The slot identifying the corresponding mimetype
@param size The size of property in bytes. For a string property prop,
this would be strlen(prop)+1
@param property The property. The target can not rely on it existing
after the function is left.
The data is always 0-terminated.
*/
typedef void (*PuglDndTargetReceiveDataFunc)(PuglView* view,
int slot,
int size, const char* property);

/**
@name Initialization
Configuration functions which must be called before creating a window.
Expand Down Expand Up @@ -562,6 +773,57 @@ typedef void (*PuglEventFunc)(PuglView* view, const PuglEvent* event);
PUGL_API void
puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc);

/*
old pugl-style dnd function setters
*/
PUGL_API void
puglSetDndSourceStatusFunc(PuglView* v, PuglDndSourceStatusFunc f);

PUGL_API void
puglSetDndSourceActionFunc(PuglView* v, PuglDndSourceActionFunc f);

PUGL_API void
puglSetDndSourceDragFunc(PuglView* v, PuglDndSourceDragFunc f);

PUGL_API void
puglSetDndSourceFinishedFunc(PuglView* v, PuglDndSourceFinishedFunc f);

PUGL_API void
puglSetDndSourceKeyFunc(PuglView* v, PuglDndSourceKeyFunc f);

PUGL_API void
puglSetDndSourceOfferTypeFunc(PuglView* v, PuglDndSourceOfferTypeFunc f);

PUGL_API void
puglSetDndSourceProvideDataFunc(PuglView* v, PuglDndSourceProvideDataFunc f);

PUGL_API void
puglSetDndTargetStatusFunc(PuglView* v, PuglDndTargetStatusFunc f);

PUGL_API void
puglSetDndTargetAcceptDropFunc(PuglView* v, PuglDndTargetAcceptDropFunc f);

PUGL_API void
puglSetDndTargetChooseTypesToLookupFunc(PuglView* v, PuglDndTargetChooseTypesToLookupFunc f);

PUGL_API void
puglSetDndTargetDropFunc(PuglView* v, PuglDndTargetDropFunc f);

PUGL_API void
puglSetDndTargetInformPositionFunc(PuglView* v, PuglDndTargetInformPositionFunc f);

PUGL_API void
puglSetDndTargetLeaveFunc(PuglView* v, PuglDndTargetLeaveFunc f);

PUGL_API void
puglSetDndTargetNoPositionInFunc(PuglView* v, PuglDndTargetNoPositionInFunc);

PUGL_API void
puglSetDndTargetOfferTypeFunc(PuglView* v, PuglDndTargetOfferTypeFunc f);

PUGL_API void
puglSetDndTargetReceiveDataFunc(PuglView* v, PuglDndTargetReceiveDataFunc f);

/**
Ignore synthetic repeated key events.
*/
Expand Down
Loading