1use crate::{
2 Drawable, EventCtx, GeomBatch, GeomBatchStack, GfxCtx, Outcome, ScreenDims, ScreenPt,
3 ScreenRectangle, StackAlignment, StackAxis, Widget, WidgetImpl, WidgetOutput,
4};
5
6const SPACE_BETWEEN_CARDS: f64 = 2.0;
7
8pub struct DragDrop<T: Copy + PartialEq> {
9 label: String,
10 cards: Vec<Card<T>>,
11 draw: Drawable,
12 state: State,
13 axis: StackAxis,
14 dims: ScreenDims,
15 top_left: ScreenPt,
16}
17
18struct Card<T: PartialEq> {
19 value: T,
20 dims: ScreenDims,
21 default_batch: GeomBatch,
22 hovering_batch: GeomBatch,
23 selected_batch: GeomBatch,
24}
25
26#[derive(PartialEq)]
27enum State {
28 Initial {
29 hovering: Option<usize>,
30 selected: Option<usize>,
31 },
32 Idle {
33 hovering: Option<usize>,
34 selected: Option<usize>,
35 },
36 Dragging {
37 orig_idx: usize,
38 drag_from: ScreenPt,
39 cursor_at: ScreenPt,
40 new_idx: usize,
41 },
42}
43
44impl<T: 'static + Copy + PartialEq> DragDrop<T> {
45 pub fn new(ctx: &EventCtx, label: &str, axis: StackAxis) -> Self {
56 DragDrop {
57 label: label.to_string(),
58 cards: vec![],
59 draw: Drawable::empty(ctx),
60 state: State::Idle {
61 hovering: None,
62 selected: None,
63 },
64 axis,
65 dims: ScreenDims::zero(),
66 top_left: ScreenPt::zero(),
67 }
68 }
69
70 pub fn into_widget(mut self, ctx: &EventCtx) -> Widget {
71 self.recalc_draw(ctx);
72 let label = self.label.clone();
73 Widget::new(Box::new(self)).named(label)
74 }
75
76 pub fn selected_value(&self) -> Option<T> {
77 let idx = match self.state {
78 State::Initial { selected, .. } | State::Idle { selected, .. } => selected,
79 State::Dragging { orig_idx, .. } => Some(orig_idx),
80 }?;
81
82 Some(self.cards[idx].value)
83 }
84
85 pub fn hovering_value(&self) -> Option<T> {
86 let idx = match self.state {
87 State::Initial { hovering, .. } | State::Idle { hovering, .. } => hovering,
88 _ => None,
89 }?;
90 Some(self.cards[idx].value)
91 }
92
93 pub fn push_card(
94 &mut self,
95 value: T,
96 dims: ScreenDims,
97 default_batch: GeomBatch,
98 hovering_batch: GeomBatch,
99 selected_batch: GeomBatch,
100 ) {
101 self.cards.push(Card {
102 value,
103 dims,
104 default_batch,
105 hovering_batch,
106 selected_batch,
107 });
108 }
109
110 pub fn set_initial_state(&mut self, selected_value: Option<T>, hovering_value: Option<T>) {
111 let selected = selected_value.and_then(|selected_value| {
112 self.cards
113 .iter()
114 .position(|card| card.value == selected_value)
115 });
116
117 let hovering = hovering_value.and_then(|hovering_value| {
118 self.cards
119 .iter()
120 .position(|card| card.value == hovering_value)
121 });
122
123 self.state = State::Initial { selected, hovering };
124 }
125
126 pub fn get_dragging_state(&self) -> Option<(usize, usize)> {
128 match self.state {
129 State::Dragging {
130 orig_idx, new_idx, ..
131 } => Some((orig_idx, new_idx)),
132 _ => None,
133 }
134 }
135}
136
137impl<T: 'static + Copy + PartialEq> DragDrop<T> {
138 fn recalc_draw(&mut self, ctx: &EventCtx) {
139 let mut stack = GeomBatchStack::from_axis(Vec::new(), self.axis);
140 stack.set_spacing(SPACE_BETWEEN_CARDS);
141
142 stack.set_alignment(if self.axis == StackAxis::Vertical {
145 StackAlignment::Left
146 } else {
147 StackAlignment::Top
148 });
149
150 let (dims, batch) = match self.state {
151 State::Initial { hovering, selected } | State::Idle { hovering, selected } => {
152 for (idx, card) in self.cards.iter().enumerate() {
153 if selected == Some(idx) {
154 stack.push(card.selected_batch.clone());
155 } else if hovering == Some(idx) {
156 stack.push(card.hovering_batch.clone());
157 } else {
158 stack.push(card.default_batch.clone());
159 }
160 }
161 let batch = stack.batch();
162 (batch.get_dims(), batch)
163 }
164 State::Dragging {
165 orig_idx,
166 drag_from,
167 cursor_at,
168 new_idx,
169 } => {
170 let orig_dims = self.cards[orig_idx].dims;
171
172 for (idx, card) in self.cards.iter().enumerate() {
173 let batch = if idx == orig_idx {
175 card.selected_batch.clone()
176 } else if idx <= new_idx && idx > orig_idx {
177 match self.axis {
179 StackAxis::Horizontal => card
180 .default_batch
181 .clone()
182 .translate(-(orig_dims.width + SPACE_BETWEEN_CARDS), 0.0),
183 StackAxis::Vertical => card
184 .default_batch
185 .clone()
186 .translate(0.0, -(orig_dims.height + SPACE_BETWEEN_CARDS)),
187 }
188 } else if idx >= new_idx && idx < orig_idx {
189 match self.axis {
191 StackAxis::Horizontal => card
192 .default_batch
193 .clone()
194 .translate(orig_dims.width + SPACE_BETWEEN_CARDS, 0.0),
195 StackAxis::Vertical => card
196 .default_batch
197 .clone()
198 .translate(0.0, orig_dims.height + SPACE_BETWEEN_CARDS),
199 }
200 } else {
201 card.default_batch.clone()
202 };
203
204 stack.push(batch);
205 }
206
207 let dims = stack.clone().batch().get_dims();
210
211 let mut dragged_batch = std::mem::take(stack.get_mut(orig_idx).unwrap());
216
217 let floating_effect_offset = 4.0;
219 dragged_batch = dragged_batch
220 .translate(
221 cursor_at.x - drag_from.x + floating_effect_offset,
222 cursor_at.y - drag_from.y - floating_effect_offset,
223 )
224 .set_z_offset(-0.1);
225 *stack.get_mut(orig_idx).unwrap() = dragged_batch;
226
227 (dims, stack.batch())
228 }
229 };
230 self.dims = dims;
231 self.draw = batch.upload(ctx);
232 }
233
234 fn mouseover_card(&self, ctx: &EventCtx) -> Option<usize> {
235 let pt = ctx.canvas.get_cursor_in_screen_space()?;
236 let mut top_left = self.top_left;
237 for (idx, Card { dims, .. }) in self.cards.iter().enumerate() {
238 if ScreenRectangle::top_left(top_left, *dims).contains(pt) {
239 return Some(idx);
240 }
241 match self.axis {
242 StackAxis::Horizontal => {
243 top_left.x += dims.width + SPACE_BETWEEN_CARDS;
244 }
245 StackAxis::Vertical => {
246 top_left.y += dims.height + SPACE_BETWEEN_CARDS;
247 }
248 }
249 }
250 None
251 }
252}
253
254impl<T: 'static + Copy + PartialEq> WidgetImpl for DragDrop<T> {
255 fn get_dims(&self) -> ScreenDims {
256 self.dims
257 }
258
259 fn set_pos(&mut self, top_left: ScreenPt) {
260 self.top_left = top_left;
261 }
262
263 fn event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput) {
264 let new_state = match self.state {
265 State::Initial { selected, hovering } => {
266 if let Some(idx) = self.mouseover_card(ctx) {
267 if hovering != Some(idx) {
268 output.outcome = Outcome::Changed(self.label.clone());
269 }
270 State::Idle {
271 hovering: Some(idx),
272 selected,
273 }
274 } else {
275 return;
278 }
279 }
280 State::Idle { hovering, selected } => match self.mouseover_card(ctx) {
281 Some(idx) if ctx.input.left_mouse_button_pressed() => {
282 let cursor = ctx.canvas.get_cursor_in_screen_space().unwrap();
283 State::Dragging {
284 orig_idx: idx,
285 drag_from: cursor,
286 cursor_at: cursor,
287 new_idx: idx,
288 }
289 }
290 maybe_idx => {
291 if hovering != maybe_idx {
292 output.outcome = Outcome::Changed(self.label.clone());
293 }
294 State::Idle {
295 hovering: maybe_idx,
296 selected,
297 }
298 }
299 },
300 State::Dragging {
301 orig_idx,
302 new_idx,
303 cursor_at,
304 drag_from,
305 } => {
306 if ctx.input.left_mouse_button_released() {
307 output.outcome =
308 Outcome::DragDropReleased(self.label.clone(), orig_idx, new_idx);
309 if orig_idx != new_idx {
310 let item = self.cards.remove(orig_idx);
311 self.cards.insert(new_idx, item);
312 }
313
314 State::Idle {
315 hovering: Some(new_idx),
316 selected: Some(new_idx),
317 }
318 } else {
319 let updated_idx = self.mouseover_card(ctx).unwrap_or(new_idx);
322 if new_idx != updated_idx {
323 output.outcome = Outcome::Changed(format!("dragging {}", self.label));
324 }
325
326 State::Dragging {
327 orig_idx,
328 new_idx: updated_idx,
329 cursor_at: ctx.canvas.get_cursor_in_screen_space().unwrap_or(cursor_at),
330 drag_from,
331 }
332 }
333 }
334 };
335
336 if self.state != new_state {
337 self.state = new_state;
338 self.recalc_draw(ctx);
339 }
340
341 match self.state {
342 State::Initial {
343 hovering: Some(_), ..
344 }
345 | State::Idle {
346 hovering: Some(_), ..
347 } => ctx.cursor_grabbable(),
348 State::Dragging { .. } => {
349 ctx.cursor_grabbing();
350 if matches!(output.outcome, Outcome::Nothing) {
351 output.outcome = Outcome::Focused(self.label.clone());
352 }
353 }
354 _ => {}
355 }
356 }
357
358 fn draw(&self, g: &mut GfxCtx) {
359 g.redraw_at(self.top_left, &self.draw);
360 }
361}