1use std::{
8 any::Any,
9 cell::Cell,
10 rc::Rc,
11 sync::{
12 Arc,
13 atomic::{AtomicU64, Ordering},
14 },
15};
16
17use gpui::{
18 Bounds, Context, Empty, IntoElement, Pixels, Point, Render, Size, Window, point, px, size,
19};
20
21use crate::Placement;
22
23use super::layout::{NodeId, PanelId};
24
25#[derive(Clone)]
34pub struct DragPanel {
35 panel: PanelId,
36 source: NodeId,
37 drag_offset: Rc<Cell<Point<Pixels>>>,
38 preview_size: Rc<Cell<Size<Pixels>>>,
39 drag_session_id: u64,
40}
41
42static NEXT_DRAG_SESSION_ID: AtomicU64 = AtomicU64::new(1);
43
44pub(crate) const ITEM_DRAG_SESSION_ID: u64 = 0;
48
49impl DragPanel {
50 pub fn new(panel: PanelId, source: NodeId) -> Self {
51 Self {
52 panel,
53 source,
54 drag_offset: Rc::new(Cell::new(Point::default())),
55 preview_size: Rc::new(Cell::new(Size::default())),
56 drag_session_id: NEXT_DRAG_SESSION_ID.fetch_add(1, Ordering::Relaxed),
57 }
58 }
59
60 pub fn panel(&self) -> PanelId {
61 self.panel
62 }
63
64 pub fn source(&self) -> NodeId {
66 self.source
67 }
68
69 pub fn drag_offset(&self) -> Point<Pixels> {
70 self.drag_offset.get()
71 }
72
73 pub fn set_drag_offset(&self, offset: Point<Pixels>) {
76 self.drag_offset.set(offset);
77 }
78
79 pub fn preview_size(&self) -> Size<Pixels> {
87 self.preview_size.get()
88 }
89
90 pub fn set_preview_size(&self, size: Size<Pixels>) {
91 self.preview_size.set(size);
92 }
93
94 pub fn drag_session_id(&self) -> u64 {
95 self.drag_session_id
96 }
97}
98
99impl Render for DragPanel {
100 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
103 Empty
104 }
105}
106
107#[derive(Clone, Debug)]
109pub struct AnyDrag {
110 value: Arc<dyn Any>,
111}
112
113impl AnyDrag {
114 pub fn new(value: impl Any) -> Self {
115 Self {
116 value: Arc::new(value),
117 }
118 }
119
120 pub fn value(&self) -> &Arc<dyn Any> {
121 &self.value
122 }
123}
124
125#[derive(Clone, Debug)]
127pub enum DropTarget {
128 Canvas,
131 Group {
137 node: NodeId,
138 placement: Option<Placement>,
139 },
140}
141
142#[derive(Clone, Copy, Debug, PartialEq)]
148pub struct DropIndicator {
149 bounds: Bounds<Pixels>,
150 placement: Option<Placement>,
151 from: DropPlaceholderBounds,
152 to: DropPlaceholderBounds,
153 drag_session_id: u64,
154 epoch: u64,
155}
156
157impl DropIndicator {
158 pub(crate) fn new(
159 bounds: Bounds<Pixels>,
160 placement: Option<Placement>,
161 from: DropPlaceholderBounds,
162 to: DropPlaceholderBounds,
163 drag_session_id: u64,
164 epoch: u64,
165 ) -> Self {
166 Self {
167 bounds,
168 placement,
169 from,
170 to,
171 drag_session_id,
172 epoch,
173 }
174 }
175
176 pub fn bounds(&self) -> Bounds<Pixels> {
178 self.bounds
179 }
180
181 pub fn placement(&self) -> Option<Placement> {
183 self.placement
184 }
185
186 pub fn from(&self) -> DropPlaceholderBounds {
188 self.from
189 }
190
191 pub fn to(&self) -> DropPlaceholderBounds {
193 self.to
194 }
195
196 pub fn drag_session_id(&self) -> u64 {
199 self.drag_session_id
200 }
201
202 pub fn epoch(&self) -> u64 {
205 self.epoch
206 }
207}
208
209#[derive(Clone, Copy, Debug, PartialEq)]
212pub struct DropPlaceholderBounds {
213 origin: Point<Pixels>,
214 size: Size<Pixels>,
215}
216
217impl DropPlaceholderBounds {
218 pub(crate) fn new(origin: Point<Pixels>, size: Size<Pixels>) -> Self {
219 Self { origin, size }
220 }
221
222 pub fn for_placement(bounds: Bounds<Pixels>, placement: Option<Placement>) -> Self {
223 let half_width = bounds.size.width * 0.5;
224 let half_height = bounds.size.height * 0.5;
225
226 match placement {
227 Some(Placement::Left) => Self {
228 origin: Point::default(),
229 size: size(half_width, bounds.size.height),
230 },
231 Some(Placement::Right) => Self {
232 origin: point(half_width, px(0.)),
233 size: size(half_width, bounds.size.height),
234 },
235 Some(Placement::Top) => Self {
236 origin: Point::default(),
237 size: size(bounds.size.width, half_height),
238 },
239 Some(Placement::Bottom) => Self {
240 origin: point(px(0.), half_height),
241 size: size(bounds.size.width, half_height),
242 },
243 None => Self {
244 origin: Point::default(),
245 size: bounds.size,
246 },
247 }
248 }
249
250 pub fn origin(&self) -> Point<Pixels> {
251 self.origin
252 }
253
254 pub fn size(&self) -> Size<Pixels> {
255 self.size
256 }
257}
258
259pub fn split_placement_at(bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<Placement> {
262 if position.x < bounds.left() + bounds.size.width * 0.35 {
263 Some(Placement::Left)
264 } else if position.x > bounds.left() + bounds.size.width * 0.65 {
265 Some(Placement::Right)
266 } else if position.y < bounds.top() + bounds.size.height * 0.35 {
267 Some(Placement::Top)
268 } else if position.y > bounds.top() + bounds.size.height * 0.65 {
269 Some(Placement::Bottom)
270 } else {
271 None
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use gpui::point;
278
279 use super::*;
280
281 #[test]
282 fn drop_placeholder_bounds_cover_each_target_placement() {
283 let bounds = gpui::Bounds {
284 origin: point(px(120.), px(80.)),
285 size: gpui::size(px(400.), px(300.)),
286 };
287
288 assert_eq!(
289 DropPlaceholderBounds::for_placement(bounds, Some(Placement::Left)),
290 DropPlaceholderBounds {
291 origin: point(px(0.), px(0.)),
292 size: gpui::size(px(200.), px(300.)),
293 }
294 );
295 assert_eq!(
296 DropPlaceholderBounds::for_placement(bounds, Some(Placement::Right)),
297 DropPlaceholderBounds {
298 origin: point(px(200.), px(0.)),
299 size: gpui::size(px(200.), px(300.)),
300 }
301 );
302 assert_eq!(
303 DropPlaceholderBounds::for_placement(bounds, Some(Placement::Top)),
304 DropPlaceholderBounds {
305 origin: point(px(0.), px(0.)),
306 size: gpui::size(px(400.), px(150.)),
307 }
308 );
309 assert_eq!(
310 DropPlaceholderBounds::for_placement(bounds, Some(Placement::Bottom)),
311 DropPlaceholderBounds {
312 origin: point(px(0.), px(150.)),
313 size: gpui::size(px(400.), px(150.)),
314 }
315 );
316 assert_eq!(
317 DropPlaceholderBounds::for_placement(bounds, None),
318 DropPlaceholderBounds {
319 origin: point(px(0.), px(0.)),
320 size: gpui::size(px(400.), px(300.)),
321 }
322 );
323 }
324
325 #[test]
326 fn split_placement_follows_the_cursor_zone() {
327 let bounds = gpui::Bounds {
328 origin: point(px(120.), px(80.)),
329 size: gpui::size(px(400.), px(300.)),
330 };
331 let at = |x: f32, y: f32| split_placement_at(bounds, point(px(x), px(y)));
333
334 assert_eq!(at(130., 230.), Some(Placement::Left));
335 assert_eq!(at(510., 230.), Some(Placement::Right));
336 assert_eq!(at(320., 90.), Some(Placement::Top));
337 assert_eq!(at(320., 370.), Some(Placement::Bottom));
338 assert_eq!(at(320., 230.), None, "centre merges into the tab group");
339 }
340
341 #[test]
342 fn split_placement_prefers_horizontal_in_the_corners() {
343 let bounds = gpui::Bounds {
344 origin: Point::default(),
345 size: gpui::size(px(400.), px(300.)),
346 };
347
348 assert_eq!(
350 split_placement_at(bounds, point(px(10.), px(10.))),
351 Some(Placement::Left)
352 );
353 assert_eq!(
354 split_placement_at(bounds, point(px(390.), px(290.))),
355 Some(Placement::Right)
356 );
357 }
358
359 #[test]
360 fn split_placement_boundaries_fall_into_the_centre() {
361 let bounds = gpui::Bounds {
362 origin: Point::default(),
363 size: gpui::size(px(400.), px(300.)),
364 };
365
366 assert_eq!(split_placement_at(bounds, point(px(140.), px(150.))), None);
368 assert_eq!(split_placement_at(bounds, point(px(260.), px(150.))), None);
369 assert_eq!(split_placement_at(bounds, point(px(200.), px(105.))), None);
370 assert_eq!(split_placement_at(bounds, point(px(200.), px(195.))), None);
371 }
372}