1use std::rc::Rc;
7
8use gpui::prelude::*;
9use gpui::{div, px, AnyElement, App, ElementId, IntoElement, SharedString, Window};
10
11use super::chip::DragChip;
12use crate::devtools::Probed;
13use crate::theme::theme;
14
15type ItemBuilder = Rc<dyn Fn(usize, &mut Window, &mut App) -> AnyElement + 'static>;
16type ReorderHandler = Rc<dyn Fn(usize, usize, &mut Window, &mut App) + 'static>;
17type Labeler = Rc<dyn Fn(usize) -> SharedString + 'static>;
18
19#[derive(Clone)]
21struct SortDrag {
22 group: SharedString,
23 index: usize,
24}
25
26#[derive(IntoElement)]
44pub struct SortableList {
45 id: ElementId,
46 group: SharedString,
47 count: usize,
48 item: ItemBuilder,
49 labeler: Option<Labeler>,
50 gap: f32,
51 on_reorder: Option<ReorderHandler>,
52}
53
54impl SortableList {
55 pub fn new<E>(
58 id: impl Into<SharedString>,
59 count: usize,
60 item: impl Fn(usize, &mut Window, &mut App) -> E + 'static,
61 ) -> Self
62 where
63 E: IntoElement,
64 {
65 let group: SharedString = id.into();
66 SortableList {
67 id: ElementId::Name(group.clone()),
68 group,
69 count,
70 item: Rc::new(move |i, window, cx| item(i, window, cx).into_any_element()),
71 labeler: None,
72 gap: 4.0,
73 on_reorder: None,
74 }
75 }
76
77 pub fn label_of(mut self, labeler: impl Fn(usize) -> SharedString + 'static) -> Self {
79 self.labeler = Some(Rc::new(labeler));
80 self
81 }
82
83 pub fn gap(mut self, gap: f32) -> Self {
85 self.gap = gap.max(0.0);
86 self
87 }
88
89 pub fn on_reorder(
91 mut self,
92 handler: impl Fn(usize, usize, &mut Window, &mut App) + 'static,
93 ) -> Self {
94 self.on_reorder = Some(Rc::new(handler));
95 self
96 }
97}
98
99impl RenderOnce for SortableList {
100 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
101 let t = theme(cx);
102 let accent = t.primary().hsla();
103
104 let mut root = div().id(self.id).flex().flex_col().gap(px(self.gap));
105 for i in 0..self.count {
106 let content = (self.item)(i, window, cx);
107 let label = match &self.labeler {
108 Some(labeler) => labeler(i),
109 None => SharedString::from(format!("Item {}", i + 1)),
110 };
111 let chip = DragChip {
112 value: SortDrag {
113 group: self.group.clone(),
114 index: i,
115 },
116 label,
117 };
118
119 let mut row = div()
120 .id(("guise-sortable-row", i))
121 .cursor_grab()
122 .border_t_2()
125 .border_color(gpui::transparent_black())
126 .on_drag(chip, |dragged: &DragChip<SortDrag>, _off, _w, cx| {
127 cx.new(|_| dragged.clone())
128 })
129 .drag_over::<DragChip<SortDrag>>(move |style, _drag, _window, _cx| {
130 style.border_color(accent)
131 })
132 .child(content);
133
134 if let Some(handler) = self.on_reorder.clone() {
135 let group = self.group.clone();
136 row = row.on_drop(move |dragged: &DragChip<SortDrag>, window, cx| {
137 if dragged.value.group == group && dragged.value.index != i {
138 handler(dragged.value.index, i, window, cx);
139 }
140 });
141 }
142 root = root.child(row);
143 }
144 root.probe("SortableList")
145 }
146}