Skip to main content

gpui_kit_grid/state/
drag.rs

1//! Dragging rows within the application: what a row drag carries, what a
2//! drop names, and what the delegate's `drag_payload` hook knows.
3//!
4//! The source: a press a [`RowDragGesture`] takes starts no drag-selection;
5//! past the gesture's threshold the grid takes the pressed row's command
6//! scope and the delegate's payload and arms the rows' wrapper
7//! (`"live-grid-rows"`) with `on_drag` for the next frame, whose next move
8//! starts GPUI's drag. The wrapper carries an empty `on_click` while the
9//! gesture is enabled, so GPUI records the press it needs. The armed drag
10//! goes on release, or in a frame with no drag and no press.
11//!
12//! The target: the wrapper follows every row drag (`on_drag_move`) and the
13//! application's types it registered: over the rows the [`RowDropPolicy`]
14//! places the drag by the line painted under the pointer, the frame draws
15//! the target by its key, the rows scroll near an edge, and the cursor says
16//! whether the grid takes it. A drop places it again where it lands and
17//! emits `LiveGridEvent::Drop`. GPUI has no "drag ended" callback: the
18//! target goes when the pointer leaves, on any release, and in a frame with
19//! no drag.
20
21use std::{any::Any, fmt, rc::Rc, sync::Arc};
22
23use gpui_kit::base::AutoScroll;
24use gpui_kit::component::ActiveTheme as _;
25use gpui_kit::{
26    AnyElement, App, AppContext as _, Bounds, Context, CursorStyle, Div, DragMoveEvent, EntityId,
27    Hsla, InteractiveElement as _, IntoElement, Modifiers, ParentElement as _, Pixels, Point,
28    Render, SharedString, Stateful, StatefulInteractiveElement as _, Styled as _, Window, div,
29    point, prelude::FluentBuilder as _, px, relative, size,
30};
31use rust_i18n::t;
32
33use super::{
34    CELL_PADDING, KeyOf, LiveGridDelegate, LiveGridEvent, LiveGridState,
35    frame::PaintedLine,
36    ghost::GhostTextStyle,
37    pinned::BodyLine,
38    selection::LineTarget,
39    stats::{profile_elapsed, profile_start},
40};
41use crate::{
42    engine::GroupPath,
43    model::GridRow,
44    policy::{
45        DropLine, NoRowDrag, NoRowDrop, RowDragGesture, RowDragPressContext, RowDropContext,
46        RowDropPolicy, SelectableLine,
47    },
48    store::StoreSnapshot,
49};
50
51/// The payload of a row drag: what GPUI carries while rows are dragged out
52/// of a grid. A drop target outside grids takes it with
53/// `on_drop::<RowDrag<K>>`.
54#[derive(Clone)]
55pub struct RowDrag<K> {
56    grid: EntityId,
57    keys: Arc<[K]>,
58    payload: DragPayload,
59}
60
61impl<K> RowDrag<K> {
62    /// The grid the rows come from.
63    pub fn grid(&self) -> EntityId {
64        self.grid
65    }
66
67    /// The dragged rows in the shown order: the command scope of the press,
68    /// taken when the drag started.
69    pub fn keys(&self) -> &Arc<[K]> {
70        &self.keys
71    }
72
73    /// What the application added (`LiveGridDelegate::drag_payload`).
74    pub fn payload(&self) -> &DragPayload {
75        &self.payload
76    }
77}
78
79impl<K> fmt::Debug for RowDrag<K> {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.debug_struct("RowDrag")
82            .field("grid", &self.grid)
83            .field("keys", &self.keys.len())
84            .field("payload", &self.payload)
85            .finish()
86    }
87}
88
89/// Application data riding along a drag, and the label of its ghost.
90#[derive(Clone, Default)]
91pub struct DragPayload {
92    data: Option<Arc<dyn Any>>,
93    label: Option<SharedString>,
94}
95
96impl DragPayload {
97    /// An empty payload: the keys alone are dragged.
98    pub fn new() -> Self {
99        Self::default()
100    }
101
102    /// Adds data for the drop target, read back with [`Self::data`].
103    pub fn with_data<T: 'static>(mut self, data: T) -> Self {
104        self.data = Some(Arc::new(data));
105        self
106    }
107
108    /// The data, if it is a `T`.
109    pub fn data<T: 'static>(&self) -> Option<&T> {
110        self.data.as_deref()?.downcast_ref()
111    }
112
113    /// Sets the text of the drag's ghost ("3 orders"); without one the
114    /// ghost says how many rows ("3 rows").
115    pub fn with_label(mut self, label: impl Into<SharedString>) -> Self {
116        self.label = Some(label.into());
117        self
118    }
119
120    /// The ghost's text, if set.
121    pub fn label(&self) -> Option<&SharedString> {
122        self.label.as_ref()
123    }
124}
125
126impl fmt::Debug for DragPayload {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        f.debug_struct("DragPayload")
129            .field("label", &self.label)
130            .field("has_data", &self.data.is_some())
131            .finish()
132    }
133}
134
135/// What was dropped on a grid.
136#[derive(Clone, Debug)]
137#[non_exhaustive]
138pub enum DropSource<K> {
139    /// Rows of a grid with this key type: this grid or another.
140    Rows(RowDrag<K>),
141    /// A value of a type the grid takes (`with_drop_payload::<T>()`), as its
142    /// data ([`DragPayload::data`]).
143    Payload(DragPayload),
144}
145
146/// Where a drop lands, by identity.
147#[derive(Clone, Debug, PartialEq, Eq)]
148#[non_exhaustive]
149pub enum DropTarget<K> {
150    /// A data row (a tree row too), and where relative to it.
151    Row {
152        /// The row.
153        key: K,
154        /// Before, after or onto it.
155        place: DropPlace,
156    },
157    /// A group caption.
158    Group(GroupPath),
159    /// The empty area below the rows, or an empty grid.
160    Empty,
161}
162
163/// Where a drop lands relative to a row.
164#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
165#[non_exhaustive]
166pub enum DropPlace {
167    /// Above the row.
168    Before,
169    /// Below the row.
170    After,
171    /// Onto the row.
172    Onto,
173}
174
175/// What [`LiveGridDelegate::drag_payload`](super::LiveGridDelegate::drag_payload)
176/// knows: the rows about to be dragged.
177pub struct RowDragContext<'a, R: GridRow> {
178    keys: &'a [R::Key],
179    pressed: &'a R::Key,
180    snapshot: &'a StoreSnapshot<R>,
181    modifiers: Modifiers,
182}
183
184impl<'a, R: GridRow> RowDragContext<'a, R> {
185    /// The rows to drag, in the shown order (the command scope).
186    pub fn keys(&self) -> &'a [R::Key] {
187        self.keys
188    }
189
190    /// The row the press was on.
191    pub fn pressed(&self) -> &'a R::Key {
192        self.pressed
193    }
194
195    /// A row of the snapshot shown when the drag started.
196    pub fn row(&self, key: &R::Key) -> Option<&'a Arc<R>> {
197        self.snapshot.row(self.snapshot.row_id(key)?)
198    }
199
200    /// The modifiers held.
201    pub fn modifiers(&self) -> Modifiers {
202        self.modifiers
203    }
204}
205
206/// Adds the listeners of one of the application's drop types to the rows'
207/// wrapper (`with_drop_payload`).
208type DropRegistrar<D> = Rc<dyn Fn(Stateful<Div>, &mut Context<LiveGridState<D>>) -> Stateful<Div>>;
209
210/// What a grid keeps for row drags: the gesture and the drag armed past its
211/// threshold; the drop policy, the application's drop types, and the drag
212/// over the rows with its target.
213pub(super) struct DragSlot<D: LiveGridDelegate> {
214    gesture: Box<dyn RowDragGesture<D::Row>>,
215    /// Set past the gesture's threshold, until the release: the rows'
216    /// wrapper carries it as `on_drag`.
217    armed: Option<RowDrag<KeyOf<D>>>,
218    drop_policy: Box<dyn RowDropPolicy<D::Row>>,
219    payloads: Vec<DropRegistrar<D>>,
220    /// The drag over the rows, from its first move inside them until it
221    /// leaves, drops or ends.
222    hover: Option<DropHover<KeyOf<D>>>,
223    /// The target of the hovering drag wants placing again from the lines a
224    /// frame paints: the rows scrolled under the pointer.
225    retarget: bool,
226    auto_scroll: AutoScroll,
227}
228
229impl<D: LiveGridDelegate> Default for DragSlot<D> {
230    fn default() -> Self {
231        Self {
232            gesture: Box::new(NoRowDrag),
233            armed: None,
234            drop_policy: Box::new(NoRowDrop),
235            payloads: Vec::new(),
236            hover: None,
237            retarget: false,
238            auto_scroll: AutoScroll::default(),
239        }
240    }
241}
242
243impl<D: LiveGridDelegate> DragSlot<D> {
244    /// Returns true if a press may drag rows: the wrapper then records
245    /// presses.
246    pub(super) fn is_enabled(&self) -> bool {
247        self.gesture.is_enabled()
248    }
249
250    /// Returns true if `press` may become a row drag.
251    pub(super) fn is_drag_press(&self, press: &RowDragPressContext<'_, D::Row>) -> bool {
252        self.gesture.is_enabled() && self.gesture.is_drag_press(press)
253    }
254
255    /// How far the pointer travels from the press before the drag starts.
256    pub(super) fn threshold(&self) -> Pixels {
257        self.gesture.threshold()
258    }
259
260    /// Drops the armed drag: the press ended.
261    pub(super) fn unarm(&mut self) {
262        self.armed = None;
263    }
264
265    /// Returns true while a drag is over the rows (`ReorderContext::
266    /// is_drag_over`).
267    pub(super) fn is_over(&self) -> bool {
268        self.hover.is_some()
269    }
270}
271
272/// What is dragged over the rows: rows of a grid with this key type, or a
273/// value of a registered type, cloned once when it came over the rows.
274#[derive(Clone)]
275enum Dragged<K> {
276    Rows(RowDrag<K>),
277    Value(Rc<dyn Any>),
278}
279
280impl<K> Dragged<K> {
281    fn as_ref(&self) -> DraggedRef<'_, K> {
282        match self {
283            Self::Rows(drag) => DraggedRef::Rows(drag),
284            Self::Value(value) => DraggedRef::Value(value.as_ref()),
285        }
286    }
287}
288
289/// [`Dragged`], borrowed: what a drop hands over.
290#[derive(Clone, Copy)]
291enum DraggedRef<'a, K> {
292    Rows(&'a RowDrag<K>),
293    Value(&'a dyn Any),
294}
295
296/// A drag over the rows: what it carries, where it was last, and where it
297/// would land.
298struct DropHover<K> {
299    dragged: Dragged<K>,
300    own: bool,
301    position: Point<Pixels>,
302    modifiers: Modifiers,
303    target: Option<DropTarget<K>>,
304}
305
306/// Returns true if `modifiers` ask to copy rather than move: Option on
307/// macOS, Ctrl elsewhere, as the platforms' file managers do. Both count,
308/// so the code is the same everywhere.
309/// The ghost of a row drag: a chip with the payload's label, or "3 rows",
310/// right of and below the pointer, in the rows' font. GPUI draws the
311/// ghost's view at the pointer less `offset`, the press's offset in the
312/// rows' wrapper, so the chip moves by it.
313struct RowDragGhost {
314    label: SharedString,
315    height: Pixels,
316    offset: Point<Pixels>,
317    text: GhostTextStyle,
318}
319
320impl Render for RowDragGhost {
321    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
322        let theme = cx.theme();
323        self.text
324            .root()
325            .pl(self.offset.x + px(12.))
326            .pt(self.offset.y + px(12.))
327            .child(
328                div()
329                    .h(self.height)
330                    .max_w(px(320.))
331                    .px(CELL_PADDING)
332                    .flex()
333                    .items_center()
334                    .overflow_hidden()
335                    .bg(theme.table_head)
336                    .text_color(theme.table_head_foreground)
337                    .border_1()
338                    .border_color(theme.border)
339                    .shadow_md()
340                    .opacity(0.9)
341                    .child(div().min_w(px(0.)).truncate().child(self.label.clone())),
342            )
343    }
344}
345
346/// The ghost's text: the payload's label, else how many rows.
347fn ghost_label<K>(drag: &RowDrag<K>) -> SharedString {
348    if let Some(label) = drag.payload().label() {
349        return label.clone();
350    }
351    match drag.keys().len() {
352        1 => t!("live_grid.drag.row").into(),
353        count => t!("live_grid.drag.rows", count = count).into(),
354    }
355}
356
357impl<D: LiveGridDelegate> LiveGridState<D> {
358    /// Sets which presses drag rows (default [`NoRowDrag`]: none;
359    /// [`SelectedRowsDrag`](crate::SelectedRowsDrag) is LiveGrid's gesture).
360    pub fn with_row_drag_gesture(mut self, gesture: impl RowDragGesture<D::Row>) -> Self {
361        self.drag.gesture = Box::new(gesture);
362        self
363    }
364
365    /// Sets where drops on the rows land (default [`NoRowDrop`]: the grid
366    /// takes none; [`DropOnRows`](crate::DropOnRows) and
367    /// [`DropBetweenRows`](crate::DropBetweenRows) are built in).
368    pub fn with_row_drop_policy(mut self, policy: impl RowDropPolicy<D::Row>) -> Self {
369        self.drag.drop_policy = Box::new(policy);
370        self
371    }
372
373    /// Takes drops of `T`, a drag payload of the application's own, besides
374    /// the rows of grids with this key type: an element with
375    /// `on_drag(T)` dropped on the rows arrives as `DropSource::Payload`,
376    /// the value cloned as its data. The drop policy places it, reading the
377    /// value with `RowDropContext::payload`.
378    pub fn with_drop_payload<T: Clone + 'static>(mut self) -> Self {
379        self.drag.payloads.push(Rc::new(|this, cx| {
380            this.on_drag_move(cx.listener(Self::on_payload_drag_move::<T>))
381                .on_drop(cx.listener(Self::on_payload_drop::<T>))
382        }));
383        self
384    }
385
386    /// Returns true while a row drag from this grid is in progress: from the
387    /// gesture's threshold to the release.
388    pub fn is_dragging_rows(&self) -> bool {
389        self.drag.armed.is_some()
390    }
391
392    /// The row drag from this grid in progress, if any. For tests.
393    #[cfg(any(test, feature = "test-support"))]
394    pub fn row_drag(&self) -> Option<&RowDrag<KeyOf<D>>> {
395        self.drag.armed.as_ref()
396    }
397
398    /// Asks the gesture whether the primary press on the data row of `key`,
399    /// at `column`, may drag rows.
400    pub(super) fn is_row_drag_press(
401        &self,
402        key: &KeyOf<D>,
403        column: super::ColumnOf<D>,
404        was_selected: bool,
405        modifiers: Modifiers,
406        click_count: usize,
407    ) -> bool {
408        if !self.drag.is_enabled() {
409            return false;
410        }
411        let Some(id) = self.snapshot.row_id(key) else {
412            return false;
413        };
414        let Some(row) = self.snapshot.row(id) else {
415            return false;
416        };
417        let line = SelectableLine::Row {
418            row: &**row,
419            branch: self.projection.branch(id),
420        };
421        self.drag.is_drag_press(&RowDragPressContext::new(
422            line,
423            column,
424            was_selected,
425            modifiers,
426            self.selection.mode(),
427            click_count,
428        ))
429    }
430
431    /// The pointer passed the gesture's threshold from a press on the row
432    /// of `pressed`: the pressed row's command scope and the delegate's
433    /// payload arm the drag for the next frame. Returns false, arming
434    /// nothing, if the row left the store or the delegate refused.
435    pub(super) fn arm_row_drag(
436        &mut self,
437        pressed: &KeyOf<D>,
438        modifiers: Modifiers,
439        cx: &mut Context<Self>,
440    ) -> bool {
441        if self.snapshot.row_id(pressed).is_none() {
442            return false;
443        }
444        let keys = self
445            .command_scope(Some(&LineTarget::Row(pressed.clone())))
446            .shared_keys();
447        let payload = self.delegate.drag_payload(
448            &RowDragContext {
449                keys: &keys,
450                pressed,
451                snapshot: &self.snapshot,
452                modifiers,
453            },
454            cx,
455        );
456        let Some(payload) = payload else {
457            return false;
458        };
459        self.drag.armed = Some(RowDrag {
460            grid: cx.entity_id(),
461            keys,
462            payload,
463        });
464        cx.notify();
465        true
466    }
467
468    /// Drops the armed drag when the press is over.
469    pub(super) fn unarm_row_drag(&mut self) {
470        self.drag.unarm();
471    }
472
473    /// A frame of a grid whose armed drag GPUI no longer carries and whose
474    /// press is over (a release the grid did not see): the drag goes.
475    pub(super) fn settle_row_drag(&mut self, cx: &mut Context<Self>) {
476        if self.drag.armed.is_some() && self.press.is_none() && !cx.has_active_drag() {
477            self.drag.unarm();
478        }
479        if self.drag.hover.is_some() && !cx.has_active_drag() {
480            self.end_drop_hover(cx);
481        }
482    }
483
484    /// The wrapper of the rows (`"live-grid-rows"`) around
485    /// `body`: it takes the body's place in the layout. While the drag
486    /// gesture is enabled it carries an empty `on_click`, so GPUI records
487    /// a press on the rows; armed, its `on_drag` starts the drag on the next
488    /// move, and the ghost draws in `ghost_text`. A div without a role: no
489    /// accessibility node between the grid and its rows. It takes no wheel:
490    /// the body scrolls as before.
491    pub(super) fn render_rows(
492        &self,
493        body: impl IntoElement,
494        ghost_text: GhostTextStyle,
495        cx: &mut Context<Self>,
496    ) -> AnyElement {
497        let row_height = self.row_height;
498        div()
499            .id("live-grid-rows")
500            .flex()
501            .flex_col()
502            .flex_grow(1.)
503            .flex_shrink(1.)
504            .flex_basis(relative(0.))
505            .w_full()
506            .when(self.drag.is_enabled(), |this| this.on_click(|_, _, _| {}))
507            // Every row drag: a target shows where it lands, or that it
508            // does not, and the source its own cursor.
509            .on_drag_move(cx.listener(Self::on_row_drag_move))
510            .when(self.drag.drop_policy.is_enabled(), |this| {
511                this.on_drop(cx.listener(Self::on_rows_drop))
512            })
513            .map(|this| {
514                self.drag
515                    .payloads
516                    .iter()
517                    .fold(this, |this, register| register(this, cx))
518            })
519            // Armed, the drag starts with the closed hand (GPUI takes the
520            // cursor of the element it starts from).
521            .when_some(self.drag.armed.clone(), |this, drag| {
522                this.cursor(CursorStyle::ClosedHand).on_drag(
523                    drag,
524                    move |drag: &RowDrag<KeyOf<D>>, offset, _: &mut Window, cx: &mut App| {
525                        let label = ghost_label(drag);
526                        cx.new(|_| RowDragGhost {
527                            label,
528                            height: row_height,
529                            offset,
530                            text: ghost_text.clone(),
531                        })
532                    },
533                )
534            })
535            .child(body)
536            .into_any_element()
537    }
538
539    /// A row drag moved anywhere in the window.
540    fn on_row_drag_move(
541        &mut self,
542        event: &DragMoveEvent<RowDrag<KeyOf<D>>>,
543        window: &mut Window,
544        cx: &mut Context<Self>,
545    ) {
546        let (position, modifiers) = (event.event.position, event.event.modifiers);
547        let inside = event.bounds.contains(&position);
548        if !inside && self.drag.hover.is_none() {
549            return;
550        }
551        let drag = event.drag(cx).clone();
552        let own = drag.grid() == cx.entity_id();
553        self.follow_drag(
554            Dragged::Rows(drag),
555            own,
556            position,
557            modifiers,
558            inside,
559            window,
560            cx,
561        );
562    }
563
564    /// A drag of the application's type `T` moved anywhere in the window.
565    /// The value is cloned once, when it comes over the rows.
566    fn on_payload_drag_move<T: Clone + 'static>(
567        &mut self,
568        event: &DragMoveEvent<T>,
569        window: &mut Window,
570        cx: &mut Context<Self>,
571    ) {
572        let (position, modifiers) = (event.event.position, event.event.modifiers);
573        let inside = event.bounds.contains(&position);
574        if !inside && self.drag.hover.is_none() {
575            return;
576        }
577        let value = match &self.drag.hover {
578            Some(DropHover {
579                dragged: Dragged::Value(value),
580                ..
581            }) if value.is::<T>() => value.clone(),
582            _ => Rc::new(event.drag(cx).clone()) as Rc<dyn Any>,
583        };
584        self.follow_drag(
585            Dragged::Value(value),
586            false,
587            position,
588            modifiers,
589            inside,
590            window,
591            cx,
592        );
593    }
594
595    /// A drag moved: over the rows its target and the cursor follow, and
596    /// near an edge the rows scroll; off them the target goes.
597    #[allow(clippy::too_many_arguments)]
598    fn follow_drag(
599        &mut self,
600        dragged: Dragged<KeyOf<D>>,
601        own: bool,
602        position: Point<Pixels>,
603        modifiers: Modifiers,
604        inside: bool,
605        window: &mut Window,
606        cx: &mut Context<Self>,
607    ) {
608        if !inside {
609            // Off this grid: the drag shows the closed hand again until
610            // another target says otherwise.
611            self.end_drop_hover(cx);
612            cx.set_active_drag_cursor_style(CursorStyle::ClosedHand, window);
613            return;
614        }
615        let started = profile_start();
616        let target = self.row_drop_target_at(dragged.as_ref(), own, position, modifiers);
617        self.stats.last_drop_target = profile_elapsed(started);
618        let cursor = match &target {
619            Some(_) if self.drag.drop_policy.is_copy(modifiers) => CursorStyle::DragCopy,
620            Some(_) => CursorStyle::ClosedHand,
621            // Over its own rows the source keeps the closed hand.
622            None if own => CursorStyle::ClosedHand,
623            None => CursorStyle::OperationNotAllowed,
624        };
625        // After every grid saw this move: a grid the pointer left resets
626        // the cursor at once, and the grid under it has the last word,
627        // whatever their order.
628        window.defer(cx, move |window, cx| {
629            if cx.active_drag_cursor_style() != Some(cursor) {
630                cx.set_active_drag_cursor_style(cursor, window);
631            }
632        });
633        let changed = self.drag.hover.as_ref().map(|hover| &hover.target) != Some(&target);
634        let entered = self.drag.hover.is_none();
635        self.drag.hover = Some(DropHover {
636            dragged,
637            own,
638            position,
639            modifiers,
640            target,
641        });
642        if self.drag.drop_policy.is_enabled() {
643            let delta = AutoScroll::compute_delta(position.y, self.rows_bounds());
644            self.drag.auto_scroll.last_drag_position = Some(position);
645            self.drag.auto_scroll.set(delta, cx, |delta, state, cx| {
646                state.auto_scroll_drop(delta, cx)
647            });
648        }
649        if changed || entered {
650            cx.notify();
651        }
652    }
653
654    /// One auto-scroll step of a drag over the rows: the next frame places
655    /// the drag again on the lines it paints.
656    fn auto_scroll_drop(&mut self, delta: Pixels, cx: &mut Context<Self>) {
657        if self.drag.hover.is_none() {
658            self.drag.auto_scroll.stop();
659            return;
660        }
661        let before = (self.scroll_row, self.scroll_offset);
662        self.scroll_rows_by(delta);
663        if (self.scroll_row, self.scroll_offset) != before {
664            self.drag.retarget = true;
665            cx.notify();
666        }
667    }
668
669    /// Places the hovering drag again, after the rows scrolled under it:
670    /// called by the frame once its lines are painted.
671    pub(super) fn retarget_drop(&mut self) {
672        if !std::mem::take(&mut self.drag.retarget) {
673            return;
674        }
675        let Some(hover) = &self.drag.hover else {
676            return;
677        };
678        let target = self.row_drop_target_at(
679            hover.dragged.as_ref(),
680            hover.own,
681            hover.position,
682            hover.modifiers,
683        );
684        if let Some(hover) = &mut self.drag.hover {
685            hover.target = target;
686        }
687    }
688
689    /// The drag left the rows, dropped or ended: its target and the
690    /// auto-scroll go, and a held order is asked about again.
691    pub(crate) fn end_drop_hover(&mut self, cx: &mut Context<Self>) {
692        self.drag.auto_scroll.stop();
693        self.drag.retarget = false;
694        if self.drag.hover.take().is_some() {
695            self.reconsider_held(cx);
696            cx.notify();
697        }
698    }
699
700    /// Where a drag at a window position lands, as the drop policy places
701    /// it; `None` refuses it. Pinned bands, group footers and totals refuse
702    /// without asking the policy; a row fading out and the area below the
703    /// rows are `DropLine::Empty`. O(1).
704    fn row_drop_target_at(
705        &self,
706        dragged: DraggedRef<'_, KeyOf<D>>,
707        own: bool,
708        position: Point<Pixels>,
709        modifiers: Modifiers,
710    ) -> Option<DropTarget<KeyOf<D>>> {
711        let policy = &self.drag.drop_policy;
712        if !policy.is_enabled() {
713            return None;
714        }
715        let BodyLine::Data(line) = self.body_line_at(position)? else {
716            return None;
717        };
718        let rows = self.rows_bounds();
719        let y = position.y - rows.origin.y + self.scroll_offset;
720        let offset = ((y - self.row_height * line as f32) / self.row_height).clamp(0., 1.);
721        let (snapshot, projection) = (&self.snapshot, &self.projection);
722        let drop_line = match self.painted.get(line) {
723            Some(PaintedLine::Row(id)) => match snapshot.row(*id) {
724                Some(row) => DropLine::Row {
725                    key: row.key(),
726                    branch: projection.branch(*id),
727                },
728                None => DropLine::Empty,
729            },
730            Some(PaintedLine::Caption(path)) => DropLine::Caption(projection.groups().find(path)?),
731            Some(PaintedLine::Footer(_)) => return None,
732            Some(PaintedLine::Ghost(_)) | None => DropLine::Empty,
733        };
734        // The last data row shown, for a drop below the rows.
735        let last_row = matches!(drop_line, DropLine::Empty)
736            .then(|| {
737                (0..projection.len())
738                    .rev()
739                    .filter_map(|ix| projection.row(ix))
740                    .find_map(|id| snapshot.row(id))
741                    .map(|row| row.key())
742            })
743            .flatten();
744        let (rows_drag, value) = match dragged {
745            DraggedRef::Rows(drag) => (Some(drag), None),
746            DraggedRef::Value(value) => (None, Some(value)),
747        };
748        policy.place(&RowDropContext::new(
749            drop_line,
750            offset,
751            rows_drag,
752            value,
753            own,
754            modifiers,
755            (
756                !self.view.sort().is_empty(),
757                projection.is_grouped(),
758                projection.is_tree(),
759            ),
760            last_row,
761            snapshot,
762        ))
763    }
764
765    /// Rows were dropped on the rows: placed again where they landed, one
766    /// `Drop` if the policy takes them.
767    fn on_rows_drop(
768        &mut self,
769        drag: &RowDrag<KeyOf<D>>,
770        window: &mut Window,
771        cx: &mut Context<Self>,
772    ) {
773        let own = drag.grid() == cx.entity_id();
774        let source = DropSource::Rows(drag.clone());
775        self.drop_on_rows(DraggedRef::Rows(drag), own, source, window, cx);
776    }
777
778    /// A value of the application's type `T` was dropped on the rows.
779    fn on_payload_drop<T: Clone + 'static>(
780        &mut self,
781        value: &T,
782        window: &mut Window,
783        cx: &mut Context<Self>,
784    ) {
785        let source = DropSource::Payload(DragPayload::new().with_data(value.clone()));
786        self.drop_on_rows(DraggedRef::Value(value), false, source, window, cx);
787    }
788
789    fn drop_on_rows(
790        &mut self,
791        dragged: DraggedRef<'_, KeyOf<D>>,
792        own: bool,
793        source: DropSource<KeyOf<D>>,
794        window: &mut Window,
795        cx: &mut Context<Self>,
796    ) {
797        let (position, modifiers) = (window.mouse_position(), window.modifiers());
798        let target = self.row_drop_target_at(dragged, own, position, modifiers);
799        self.end_drop_hover(cx);
800        if let Some(target) = target {
801            cx.emit(LiveGridEvent::Drop {
802                source,
803                target,
804                modifiers,
805            });
806        }
807    }
808
809    /// The indicator of the hovering drag's target, by its key on the lines
810    /// this frame painted: a row or a caption filled and framed, a 2 px line
811    /// between rows, under the last row for the empty area, the rows' frame
812    /// in an empty grid. Across every zone, within `rows`.
813    pub(super) fn drop_indicator(
814        &self,
815        rows: Bounds<Pixels>,
816        fill_color: Hsla,
817        border: Hsla,
818    ) -> Vec<(Bounds<Pixels>, Hsla)> {
819        let Some(target) = self
820            .drag
821            .hover
822            .as_ref()
823            .and_then(|hover| hover.target.as_ref())
824        else {
825            return Vec::new();
826        };
827        let top = |line: usize| rows.origin.y + self.row_height * line as f32 - self.scroll_offset;
828        let line_of =
829            |painted: &dyn Fn(&PaintedLine) -> bool| self.painted.iter().position(painted);
830        let mut quads = Vec::new();
831        let bar = |y: Pixels, quads: &mut Vec<_>| {
832            quads.push((
833                Bounds::new(
834                    point(rows.origin.x, y - px(1.)),
835                    size(rows.size.width, px(2.)),
836                ),
837                border,
838            ));
839        };
840        let frame = |area: Bounds<Pixels>, fill: Option<Hsla>, quads: &mut Vec<_>| {
841            if let Some(color) = fill {
842                quads.push((area, color));
843            }
844            let (w, h, b) = (area.size.width, area.size.height, px(2.));
845            let origin = area.origin;
846            quads.extend([
847                (Bounds::new(origin, size(w, b)), border),
848                (
849                    Bounds::new(point(origin.x, origin.y + h - b), size(w, b)),
850                    border,
851                ),
852                (Bounds::new(origin, size(b, h)), border),
853                (
854                    Bounds::new(point(origin.x + w - b, origin.y), size(b, h)),
855                    border,
856                ),
857            ]);
858        };
859        let line_area = |line: usize| {
860            Bounds::new(
861                point(rows.origin.x, top(line)),
862                size(rows.size.width, self.row_height),
863            )
864        };
865        match target {
866            DropTarget::Row { key, place } => {
867                let Some(id) = self.snapshot.row_id(key) else {
868                    return quads;
869                };
870                let Some(line) = line_of(&|painted| painted.row() == Some(id)) else {
871                    return quads;
872                };
873                match place {
874                    DropPlace::Before => bar(top(line), &mut quads),
875                    DropPlace::After => bar(top(line + 1), &mut quads),
876                    _ => frame(line_area(line), Some(fill_color), &mut quads),
877                }
878            }
879            DropTarget::Group(path) => {
880                if let Some(line) = line_of(&|painted| painted.group() == Some(path)) {
881                    frame(line_area(line), Some(fill_color), &mut quads);
882                }
883            }
884            _ => {
885                let shown = self
886                    .painted
887                    .iter()
888                    .rposition(|painted| !matches!(painted, PaintedLine::Ghost(_)));
889                match shown {
890                    Some(last) => bar(top(last + 1), &mut quads),
891                    None => frame(rows, None, &mut quads),
892                }
893            }
894        }
895        // Within the rows' area: a line scrolled half out is cut.
896        quads
897            .into_iter()
898            .filter_map(|(bounds, color)| {
899                let bounds = bounds.intersect(&rows);
900                (bounds.size.height > px(0.) && bounds.size.width > px(0.))
901                    .then_some((bounds, color))
902            })
903            .collect()
904    }
905
906    /// Escape during a row drag from this grid: GPUI drops it, no `Drop`
907    /// comes, and the selection stays. Returns false without one.
908    pub(super) fn cancel_row_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
909        if self.drag.armed.is_none() {
910            return false;
911        }
912        self.drag.unarm();
913        cx.stop_active_drag(window);
914        cx.notify();
915        true
916    }
917}
918
919#[cfg(test)]
920mod tests {
921    use super::*;
922
923    #[test]
924    fn test_drag_payload() {
925        let payload = DragPayload::new();
926        assert!(payload.label().is_none() && payload.data::<u32>().is_none());
927        let payload = payload.with_data(7u32).with_label("3 orders");
928        assert_eq!(payload.data::<u32>(), Some(&7));
929        assert_eq!(payload.data::<i64>(), None);
930        assert_eq!(payload.label().map(AsRef::as_ref), Some("3 orders"));
931        let debug = format!("{payload:?}");
932        assert!(debug.contains("3 orders") && debug.contains("has_data: true"));
933        assert_ne!(
934            DropTarget::Row {
935                key: 1,
936                place: DropPlace::Before
937            },
938            DropTarget::Empty
939        );
940    }
941}