How to write a drag-n-drop reorderable gui?

Here is a very crude first implementation (code at the bottom):

demonstration

(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 order
    • iList: The list of indices, in the same order as list
    • indices: The current positions of the elements (given in the original order)
    • dragged: The index of the currently dragged item, or None
    • curPos: The current position of the insertion bar
    • cursor: 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]
     }
   }]
 ]

enter image description here

  • https://github.com/kubaPod/MGUI