Drag and drop
Rows can be dragged out of a grid and dropped onto another grid, onto a group caption, or onto any part of the application that takes them. The grid draws the gesture and reports the drop; moving or copying the data is the application’s. The orders example drags selected orders into a basket grid, or onto an account’s caption to move them to that account.
Turning it on
Section titled “Turning it on”Row dragging is off by default (NoRowDrag, NoRowDrop): without a drop target the gesture would only get in the way of drag-selection. The source grid sets a drag gesture, the target grid a drop policy. orders’ grid does both (the last two lines):
let grid = LiveGridState::new(model::schema(), Orders { commands }, store, cx) .with_selection_mode(SelectionMode::RowSet) .with_change_paint(gpui_kit_grid::ChangePaint::Directional(DirectionalChanges)) .with_editor(OrderColumn::Side, SelectEditor::new(options(&model::SIDES))) .with_editor(OrderColumn::Tif, SelectEditor::new(options(&model::TIFS))) .with_editor( OrderColumn::Quantity, NumberEditor::new().with_min(OwnedValue::Int(1)), ) .with_editor(OrderColumn::Limit, NumberEditor::new().with_scale(2)) .with_editor(OrderColumn::Expiry, DateEditor::date()) .with_editor(OrderColumn::Active, CheckboxEditor) .with_editor(OrderColumn::Note, TextEditor::new().with_max_length(80)) .with_row_drag_gesture(SelectedRowsDrag::new()) .with_row_drop_policy(AccountDrop);and its basket takes drops on its rows:
let basket = cx.new(|cx| { LiveGridState::new_standalone(basket_schema(), Basket, StoreOptions::default(), cx) .with_selection_mode(SelectionMode::RowSet) .with_row_drop_policy(DropOnRows)});The gesture
Section titled “The gesture”SelectedRowsDrag is a two-phase gesture: a press on a row that is already selected drags the selection once the pointer is 10 px away (with_threshold changes the distance); a press on an unselected row selects it and drag-selects as before.
LiveGridDelegate::drag_payload adds application data and the drag ghost’s label (DragPayload::new().with_label("3 orders")); the keys always come along.
Where a drop lands
Section titled “Where a drop lands”| Policy | Lands |
|---|---|
NoRowDrop (default) |
Nowhere: the grid takes no drop |
DropOnRows |
On rows, group captions and the empty area |
DropBetweenRows |
Before or after a row by the half the pointer is in; in a tree the top third is before, the middle onto the row, the bottom third after; onto group captions. Refuses a sorted flat view, where a place between rows would not hold |
Your own RowDropPolicy |
Wherever place says |
orders takes drops only on an account’s caption:
/// Drops on the orders grid land on account captions only: the orders move/// to that account.struct AccountDrop;
impl RowDropPolicy<Order> for AccountDrop { fn place(&self, drop: &RowDropContext<'_, Order>) -> Option<DropTarget<u64>> { match drop.line() { DropLine::Caption(group) if group.level_id().as_str() == OrderColumn::Account.id() && drop.rows().is_some() => { Some(DropTarget::Group(group.path().clone())) } _ => None, } }}While a drag is over a grid, the target is highlighted, the rows scroll near an edge, and the cursor shows whether the grid takes the drop. Pinned rows, group footers and totals never take one. Escape cancels the drag.
What a grid takes
Section titled “What a grid takes”A grid takes rows from grids with the same key type, and values of types registered with with_drop_payload::<T>(), so a panel or another part of the application can be the source or the target.
The drop
Section titled “The drop”The grid dropped on emits LiveGridEvent::Drop { source, target, modifiers }. orders moves the dropped orders to the account of the caption:
LiveGridEvent::Drop { source, target, .. } => { // Orders dropped on an account's caption move to it. if let (DropSource::Rows(drag), DropTarget::Group(path)) = (source, target) && let Some((_, OwnedValue::Text(account))) = path.levels().first() { let account = OwnedValue::Text(account.clone()); let writer = self.store.writer(); let snapshot = writer.snapshot(); for key in drag.keys().iter() { if let Some(order) = snapshot .get(key) .and_then(|order| order.with(OrderColumn::Account, &account)) { writer.upsert(order); } } self.last_drop = Some( format!( "Moved {} to {}", orders_label(drag.keys().len()), value_text(&account) ) .into(), ); }}With Option or Ctrl held the cursor is the copy cursor (RowDropPolicy::is_copy changes the rule), but copy or move is still the application’s choice, from modifiers.
Limits
Section titled “Limits”- Rows do not leave the application as files.
- A drop onto an element in a cell, or onto an open editor, is not taken: GPUI drops only onto the element under the pointer.
- Starting a drag of 100k selected rows takes about 4 ms on the UI thread.
Apache-2.0, built on gpui-kit. Repository Sponsor More from GigLabo Privacy Licenses