Animated Drag’n’Drop thoughts

So I’ve been tasked to try an animated approach at Drag’n’Drop to rearrange child widgets inside a specific container widget in libegg (EggSpreadTable), and I’m throwing this out in public just in case someone might have some better ideas than I do currently.

The EggSpreadTable is something like a GtkTable or GtkGrid that takes a list of widgets and displays them in a fixed number of columns, the spread table “spreads” out widgets evenly across the columns in order to consume the least height as possible (important thing to keep in mind is that widgets can shift across columns to make better use of space).

The desired effect is the one produced when running libegg/toolbareditor/test-toolbar-editor… which uses gtk_toolbar_set_drop_highlight_item(), an obscure and highly complex piece of code which is responsible for highlighting potential drop targets in the toolbar by animating other items out of the way.

My plan so far is a little abstract but I’m going to have to write code now and see if it works:

  • When a drag’n’drop (DnD) operation is in effect, and “drag-motion” is being emitted, I think I’ll have to “lock” the spread table so that widgets don’t jump from column to column, this will avoid hovering a drag source at the end of one column and having the animation show the widget at the beginning of the following column, which I think would be unintuitive.
  • When performing DnD animations, not only will we have to animate widgets up and down inside the potential “drop column”, but we’ll have to call gtk_widget_queue_resize() repeatedly whilst the spread table will have to grow in order to fit the new drag source into the column somewhere.
  • To look more intuitive, when a drag begins the drag source widget will have to “slide out” of it’s parent spread table, which will require the same animation technique whenever a drag begins. Likewise, when a drag fails the widget should be animated back into place.
  • Once a widget is successfully dropped in a said spread table (widgets can be dragged and dropped from one spread table to another), then the arrangement of widgets will still be invalid… the target spread table will have to unlock… at this point it’s also probably best to animate the rearranging of widgets (otherwise you get a nice animated DnD experience and when you drop you get this “snap” where widgets are rearranged in the target container).
  • Another complication is that EggSpreadTable is a height-for-width container, the size and space that the target placeholder will use will depend on requesting the size of the the actual widget being dragged (it’s even possible the spread table will have to grow in width & height to fit the animation).
  • And last but not least, of course it has to use GTK+’s horrid Drag’n’Drop apis.

The business logic will surely be horrid in all of this, many states will have to be managed and juggled while performing these various animations… not to mention the whole operation will require resizing of the spread tables in play (causing the whole interface to shift around, kind of like having a GtkInfoBar in play).

The toolbar code is evidently much more obvious, it only lines up a list of fixed size widgets in a row from left to right, when the widgets (tool items) don’t fit the toolbar anymore, an arrow is displayed and the rest of the items are placed in a drop down menu.

So all of this to say… anyone have any better ideas ?

6 Responses to “Animated Drag’n’Drop thoughts”

  1. Johannes says:

    I tried to do something like this for glom and ended up with rather horrible code. Drag and Drop is pretty difficult to get right but I hope your code will be much better!

  2. tvb says:

    @jhs: yes, it’s indeed difficult to get right.

    I spiked on it today and came up with something half working, I wrote an EggPlaceholder object which basically gets inserted into the spread table at potential drop sites, the object comes with _animate_in() and _animate_out() apis and it’s basically a drawing area derived widget (so you could potentially connect to the “draw” signal and draw something appropriate in the drop zone).

    I think having this separate object will at least help with code readability, but there are many things I still need to deal with, currently I have not implemented any “lock” which I mentioned in my post to keep widgets in their respective columns while DnD is happening, so I am still getting the expected “race”/”flicker” that happens when the spread table repositions it’s children at the same time as “drag-motion” callbacks are adding/removing these placeholders.

    I’m not sure if my code will be so much better/more readable, I still have a dozen special cases to handle with regards to cleaning up the states when drags are finished and handling drag’n’drop from one spread table to another, but at least I’m quite sure that the approach will work after a couple more days focusing on this.

  3. > When a drag’n’drop (DnD) operation is in effect, and “drag-motion” is being emitted, I think
    > I’ll have to “lock” the spread table so that widgets don’t jump from column to colum

    Why?

    Your stragegy of having a placeholder item sounds roughly like what Johannes did (for me) with the older FlowTable code in Glom, though that never quite worked properly and the code was mixed in with unrelated stuff. Hopefully yours will be even better.

  4. tvb says:

    Murray,
    The reason I suspected that locking the table so that widgets don’t get shuffled during DnD is because when widgets get shuffled around like that; it leaves the mouse pointer at a random location (over another widget for instance) at times.

    I tried it so far without the locking, but as I mentioned in my other comment I’m getting some competition going on:
    – Placeholder starts resizing (growing) at mouse position between 2
    widgets
    – Once the placeholder is large enough, the spread table reconfigures
    – When the spread table reconfigures it leaves the mouse position
    hovering over a new child widget
    – Because the mouse position is now over a new widget, the current
    placeholder starts to animate out and a new one starts animating in at
    the new index.

    And this happens repeatedly over and over causing a weird glitchy looking
    effect (this problem happens particularly when dragging a tall widget through an area with many short widgets).

    Maybe Johannes found a different solution to this problem ?

    It also may be helpful if someone could send me a link to the older glom code which was doing this, admittedly I have not seen it.

  5. > The reason I suspected that locking the table so that widgets don’t get shuffled during DnD
    > is because when widgets get shuffled around like that; it leaves the mouse pointer at a
    > random location (over another widget for instance) at times

    Fair enough.

    > It also may be helpful if someone could send me a link to the older glom code which was >
    > doing this, admittedly I have not seen it.

    I strongly believe that it’s better to take a fresh approach to this, but:
    http://git.gnome.org/browse/glom/tree/glom/utility_widgets/flowtable_dnd.cc?h=glom-1-14
    though that code’s requirements leaked all over many other source files.

  6. johny says:

    1. Create a layout table.
    2. Setup name for each position.
    3. Create widget dragging code.
    3.a Create temporary variable for old position (hidden content only of old position).
    3.b Create temporary variable for current position (dragging content & box).
    3.c Create temporary variable for new position (highlight box only new position).
    4. While 3.a hidden content only, all others box position didn’t float/shifted.
    5. After 3.c complete, others box can be shifted.

    If you want others box not shifted, you can replace 3.a hidden content with display none ONLY AFTER 3.c new position set up.