How to write a drag-n-drop reorderable gui?
Here is a very crude first implementation (code at the bottom):
(note that the updated version is called as `dragDropList[Dynamic@l)
Some notes:
- The black box serves both as insertion marker and as spacer to move the other items out of the way - obviously, it will need some better styling
- I'm not sure what the best size for the insertion point is - one option is to make it the same size as the item being moved (not sure how to do that though)
- As you can see, there is no smooth animation - not sure whether this one is feasible with any kind of acceptable performance
- The insertion bar is the item currently being moved - this makes re-insertion very easy, since we just have to change the displayed content back. Also, we never have to add stuff to the list, just reorder it
- The insertion bar is moved every time the cursor is over another item
- As can be seen, there is some flickering in the order of the items at some points - this is caused by the fact that reordering the items can sometimes bring another item below the cursor (instead of the insertion bar), causing repeated reordering
- The state of the control lives in several variables:
list
: The list of items, in their current orderiList
: The list of indices, in the same order aslist
indices
: The current positions of the elements (given in the original order)dragged
: The index of the currently dragged item, orNone
curPos
: The current position of the insertion barcursor
: The cursor to show (includes the moved item)
BeginPackage["dragDropList`"];
dragDropList;
Begin["`Private`"];
dragDropList[Dynamic@var_, items_] :=
Panel@DynamicModule[
{
set = (var = #) &,
rawItems = items,
list,
iList,
indices = Range@Length@items,
dragged = None,
curPos,
cursor = "Arrow",
defCursor =
Graphics[{Arrowheads[0.7], Arrow[{{0, 0}, {-.5, 1}}]},
ImageSize -> 16, PlotRange -> {{-1, 0}, {0, 2}}]
},
set@rawItems;
iList = indices;
list = MapIndexed[
EventHandler[
Dynamic@If[
dragged === #2,
Graphics[Rectangle[{0, 0}, {1, 1}], AspectRatio -> Full,
ImageSize -> {100, 30}],
#
],
{
"MouseDown" :> (
dragged = #2;
curPos = indices[[dragged]];
FrontEndExecute[
FrontEnd`SetMouseAppearance[
cursor = Overlay[{#, defCursor}, Alignment -> Center]]]
),
"MouseEntered" :> (
If[curPos =!= indices[[#2]] && dragged =!= None,
With[
{newPos = indices[[#2]]},
{iList, list} =
Transpose[({t, d} \[Function]
Insert[d, First@t, newPos]) @@
TakeDrop[Transpose@{iList, list}, {curPos}]];
indices = Ordering@iList;
set@rawItems[[iList]];
curPos = newPos
]
]
)
}
] & @@ {#, #2[[1]]} &,
rawItems
];
Deploy@EventHandler[
Pane[
Dynamic@Column@list,
{Automatic, Automatic},
Scrollbars -> Automatic
],
{
"MouseUp" :> (
dragged = None;
FrontEndExecute[
FrontEnd`SetMouseAppearance[cursor = "Arrow"]]
),
"MouseEntered" :> FrontEndExecute[FrontEnd`SetMouseAppearance[]],
"MouseExited" :>
FrontEndExecute[FrontEnd`SetMouseAppearance[cursor]]
},
PassEventsDown -> True
]
]
End[];
EndPackage[];
Dynamic@list
dragDropList[Dynamic@list,Panel/@Table[RandomWord[],10]]
I once approached this. I never finished it so let me know if you face any issues:
ResourceFunction["GitHubInstall"]["kubapod", "mgui"]
<< MGUI`
And here is an example:
DynamicModule[{ labels = Range[7] }
, labels[[1]] = Style[1, "Section"]
; Grid[{
{ "Default", "ContinuousAction", "", "ref"}
, { MSorter[Dynamic@labels]
, MSorter[Dynamic@labels, ContinuousAction -> True]
, Button["reset", labels = Range[7], ImageSize -> Small]
, Dynamic@Column[Pane /@ labels]
}
}]
]
- https://github.com/kubaPod/MGUI