Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Drag & Drop

There are two kinds of drag: a pointer drag inside the window (reordering a list, moving tabs, docking panes), and files or text dropped in from another application. The hooks only report what happened. Applying a drop to your data is up to you.

Reordering a list

useReorder takes the current order of item ids and each item’s response, in drawn order:

chips :: Var [Int] -> NanoUI ()
chips orderVar = rowWith (gap 6) $ do
  order <- readVar orderVar -- orderVar <- newVar [0 .. 4] in setup
  drawn <- forM order $ \i -> (i,) <$> chip i
  r <- useReorder order drawn
  writeVar orderVar (reorderOrder r)

reorderOrder changes on the frame the item is dropped. Draw from reorderPreview instead to move the other items aside during the drag. reorderDragging is the held item, and reorderMoved is set once the press has moved past the drag threshold, so a click can be ignored.

Gestures with useDrag

For anything else, useDrag tracks a thresholded drag across keyed sources. Allocate the handle once with newDrag in setup. Here it reorders tabs, with insertionIndex turning the pointer into a slot in the strip (Nothing outside it):

tabStrip :: DragHandle Text -> Var Text -> [Tab Text ()] -> NanoUI ()
tabStrip dragHandle active documents = do
  headers <- tabBar' active documents
  gesture <- useDrag dragHandle (tabHeaders headers)
  for_ gesture $ \d -> when (dragPhase d == DragReleased) $ do
    let rest = filter ((/= dragPayload d) . fst) (tabHeaders headers)
    for_ (insertionIndex DragAxisX (tabStripRect headers) (map (respRect . snd) rest) (dragAt d)) $
      moveTab (dragPayload d)

dragPhase is DragStarted, Dragging, DragReleased, or DragCancelled (Escape, a lost hold, or the source disappearing). The last two last one frame. Commit only on DragReleased.

Files and text from other applications

The SDL backend delivers the operating system’s drag and drop; the RGFW backend does not. dropZone draws a panel and reports the files and text dropped on it:

fileDrop :: NanoUI ()
fileDrop = do
  (_, _, target) <- dropZone (fillW . padAll 12) (label "Drop files here")
  when (dropReceived target) (mapM_ openFile (dropFiles target))

dropHovered is true while a drag is over the zone. useDrop rect does the same for any rectangle. Only the topmost layer receives the drop, so a zone behind a modal gets nothing.

Pane grids

paneGrid lays out dockable, splittable panes. Its response’s pgrDropTarget is the destination under the pointer, which pairs with a useDrag gesture:

dockPanes :: PaneGridConfig -> Maybe (Drag Word64) -> NanoUI ()
dockPanes cfg gesture = do
  panes <- paneGrid cfg
  case (gesture, pgrDropTarget panes) of
    (Just d, Just target) | dragPhase d == DragReleased -> void (commitPaneDrop target)
    _ -> pure ()

pgdLocation is BesidePane paneId edge or OutsideGrid edge; pgdRect and pgdPane help validate the drop. Save pgrTree when pgrCommitted is set.