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, Copy, Debug, PartialEq)]
131pub struct DropTarget {
132 node: NodeId,
133 placement: Option<Placement>,
134}
135
136impl DropTarget {
137 pub(crate) fn new(node: NodeId, placement: Option<Placement>) -> Self {
138 Self { node, placement }
139 }
140
141 pub fn node(&self) -> NodeId {
142 self.node
143 }
144
145 pub fn placement(&self) -> Option<Placement> {
146 self.placement
147 }
148}
149
150#[derive(Clone, Copy, Debug, PartialEq)]
156pub struct DropIndicator {
157 bounds: Bounds<Pixels>,
158 placement: Option<Placement>,
159 from: DropPlaceholderBounds,
160 to: DropPlaceholderBounds,
161 drag_session_id: u64,
162 epoch: u64,
163}
164
165impl DropIndicator {
166 pub(crate) fn new(
167 bounds: Bounds<Pixels>,
168 placement: Option<Placement>,
169 from: DropPlaceholderBounds,
170 to: DropPlaceholderBounds,
171 drag_session_id: u64,
172 epoch: u64,
173 ) -> Self {
174 Self {
175 bounds,
176 placement,
177 from,
178 to,
179 drag_session_id,
180 epoch,
181 }
182 }
183
184 pub fn bounds(&self) -> Bounds<Pixels> {
186 self.bounds
187 }
188
189 pub fn placement(&self) -> Option<Placement> {
191 self.placement
192 }
193
194 pub fn from(&self) -> DropPlaceholderBounds {
196 self.from
197 }
198
199 pub fn to(&self) -> DropPlaceholderBounds {
201 self.to
202 }
203
204 pub fn drag_session_id(&self) -> u64 {
207 self.drag_session_id
208 }
209
210 pub fn epoch(&self) -> u64 {
213 self.epoch
214 }
215}
216
217#[derive(Clone, Copy, Debug, PartialEq)]
220pub struct DropPlaceholderBounds {
221 origin: Point<Pixels>,
222 size: Size<Pixels>,
223}
224
225impl DropPlaceholderBounds {
226 pub(crate) fn new(origin: Point<Pixels>, size: Size<Pixels>) -> Self {
227 Self { origin, size }
228 }
229
230 pub fn for_placement(bounds: Bounds<Pixels>, placement: Option<Placement>) -> Self {
231 let half_width = bounds.size.width * 0.5;
232 let half_height = bounds.size.height * 0.5;
233
234 match placement {
235 Some(Placement::Left) => Self {
236 origin: Point::default(),
237 size: size(half_width, bounds.size.height),
238 },
239 Some(Placement::Right) => Self {
240 origin: point(half_width, px(0.)),
241 size: size(half_width, bounds.size.height),
242 },
243 Some(Placement::Top) => Self {
244 origin: Point::default(),
245 size: size(bounds.size.width, half_height),
246 },
247 Some(Placement::Bottom) => Self {
248 origin: point(px(0.), half_height),
249 size: size(bounds.size.width, half_height),
250 },
251 None => Self {
252 origin: Point::default(),
253 size: bounds.size,
254 },
255 }
256 }
257
258 pub fn origin(&self) -> Point<Pixels> {
259 self.origin
260 }
261
262 pub fn size(&self) -> Size<Pixels> {
263 self.size
264 }
265}
266
267pub fn split_placement_at(bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<Placement> {
270 if position.x < bounds.left() + bounds.size.width * 0.35 {
271 Some(Placement::Left)
272 } else if position.x > bounds.left() + bounds.size.width * 0.65 {
273 Some(Placement::Right)
274 } else if position.y < bounds.top() + bounds.size.height * 0.35 {
275 Some(Placement::Top)
276 } else if position.y > bounds.top() + bounds.size.height * 0.65 {
277 Some(Placement::Bottom)
278 } else {
279 None
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use gpui::point;
286
287 use super::*;
288
289 #[test]
290 fn drop_placeholder_bounds_cover_each_target_placement() {
291 let bounds = gpui::Bounds {
292 origin: point(px(120.), px(80.)),
293 size: gpui::size(px(400.), px(300.)),
294 };
295
296 assert_eq!(
297 DropPlaceholderBounds::for_placement(bounds, Some(Placement::Left)),
298 DropPlaceholderBounds {
299 origin: point(px(0.), px(0.)),
300 size: gpui::size(px(200.), px(300.)),
301 }
302 );
303 assert_eq!(
304 DropPlaceholderBounds::for_placement(bounds, Some(Placement::Right)),
305 DropPlaceholderBounds {
306 origin: point(px(200.), px(0.)),
307 size: gpui::size(px(200.), px(300.)),
308 }
309 );
310 assert_eq!(
311 DropPlaceholderBounds::for_placement(bounds, Some(Placement::Top)),
312 DropPlaceholderBounds {
313 origin: point(px(0.), px(0.)),
314 size: gpui::size(px(400.), px(150.)),
315 }
316 );
317 assert_eq!(
318 DropPlaceholderBounds::for_placement(bounds, Some(Placement::Bottom)),
319 DropPlaceholderBounds {
320 origin: point(px(0.), px(150.)),
321 size: gpui::size(px(400.), px(150.)),
322 }
323 );
324 assert_eq!(
325 DropPlaceholderBounds::for_placement(bounds, None),
326 DropPlaceholderBounds {
327 origin: point(px(0.), px(0.)),
328 size: gpui::size(px(400.), px(300.)),
329 }
330 );
331 }
332
333 #[test]
334 fn split_placement_follows_the_cursor_zone() {
335 let bounds = gpui::Bounds {
336 origin: point(px(120.), px(80.)),
337 size: gpui::size(px(400.), px(300.)),
338 };
339 let at = |x: f32, y: f32| split_placement_at(bounds, point(px(x), px(y)));
341
342 assert_eq!(at(130., 230.), Some(Placement::Left));
343 assert_eq!(at(510., 230.), Some(Placement::Right));
344 assert_eq!(at(320., 90.), Some(Placement::Top));
345 assert_eq!(at(320., 370.), Some(Placement::Bottom));
346 assert_eq!(at(320., 230.), None, "centre merges into the tab group");
347 }
348
349 #[test]
350 fn split_placement_prefers_horizontal_in_the_corners() {
351 let bounds = gpui::Bounds {
352 origin: Point::default(),
353 size: gpui::size(px(400.), px(300.)),
354 };
355
356 assert_eq!(
358 split_placement_at(bounds, point(px(10.), px(10.))),
359 Some(Placement::Left)
360 );
361 assert_eq!(
362 split_placement_at(bounds, point(px(390.), px(290.))),
363 Some(Placement::Right)
364 );
365 }
366
367 #[test]
368 fn split_placement_boundaries_fall_into_the_centre() {
369 let bounds = gpui::Bounds {
370 origin: Point::default(),
371 size: gpui::size(px(400.), px(300.)),
372 };
373
374 assert_eq!(split_placement_at(bounds, point(px(140.), px(150.))), None);
376 assert_eq!(split_placement_at(bounds, point(px(260.), px(150.))), None);
377 assert_eq!(split_placement_at(bounds, point(px(200.), px(105.))), None);
378 assert_eq!(split_placement_at(bounds, point(px(200.), px(195.))), None);
379 }
380}