1use std::rc::Rc;
2
3use sequoia_core::{Color, Length, Point, Rect, Size};
4use sequoia_layout::LayoutStyle;
5
6use crate::any::AnyView;
7use crate::dnd::{DragPayload, DropPosition};
8use crate::view::{EventCtx, PaintCtx, View};
9use crate::views::{Column, Row, Unit};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub enum EdgeSide {
13 Left,
14 Right,
15 Top,
16 Bottom,
17}
18
19impl EdgeSide {
20 pub const ALL: [EdgeSide; 4] = [
21 EdgeSide::Left,
22 EdgeSide::Right,
23 EdgeSide::Top,
24 EdgeSide::Bottom,
25 ];
26
27 pub fn is_vertical(self) -> bool {
28 matches!(self, EdgeSide::Left | EdgeSide::Right)
29 }
30
31 pub fn is_horizontal(self) -> bool {
32 !self.is_vertical()
33 }
34}
35
36pub const EDGE_HINT_PX: f32 = 28.0;
37
38pub const EDGE_GHOST_PX: f32 = 72.0;
39
40pub const EDGE_LINE_PX: f32 = 3.0;
41
42pub const UNDOCK_THRESHOLD_PX: f32 = 6.0;
43
44pub const MIN_VISIBLE_W: f32 = 120.0;
45pub const MIN_VISIBLE_H: f32 = 34.0;
46
47pub fn edge_hint_at(pos: Point, window: Size, threshold: f32) -> Option<EdgeSide> {
48 let t = if threshold.is_finite() && threshold > 0.0 {
49 threshold
50 } else {
51 EDGE_HINT_PX
52 };
53 if pos.x < t {
54 Some(EdgeSide::Left)
55 } else if pos.x > window.width - t {
56 Some(EdgeSide::Right)
57 } else if pos.y < t {
58 Some(EdgeSide::Top)
59 } else if pos.y > window.height - t {
60 Some(EdgeSide::Bottom)
61 } else {
62 None
63 }
64}
65
66pub fn edge_ghost_rect(side: EdgeSide, window: Size, thickness: f32) -> Rect {
67 let t = if thickness.is_finite() && thickness > 0.0 {
68 thickness
69 } else {
70 EDGE_GHOST_PX
71 };
72 match side {
73 EdgeSide::Left => Rect::from_xywh(0.0, 0.0, t, window.height),
74 EdgeSide::Right => Rect::from_xywh((window.width - t).max(0.0), 0.0, t, window.height),
75 EdgeSide::Top => Rect::from_xywh(0.0, 0.0, window.width, t),
76 EdgeSide::Bottom => Rect::from_xywh(0.0, (window.height - t).max(0.0), window.width, t),
77 }
78}
79
80pub fn clamp_card_top_left(global: Point, grab_offset: (f32, f32), window: Size) -> (f32, f32) {
81 let nx = global.x - grab_offset.0;
82 let ny = global.y - grab_offset.1;
83 let max_x = (window.width - MIN_VISIBLE_W).max(0.0);
84 let max_y = (window.height - MIN_VISIBLE_H).max(0.0);
85 (nx.clamp(0.0, max_x), ny.clamp(0.0, max_y))
86}
87
88pub fn exceeded_undock(origin: Point, current: Point) -> bool {
89 let dx = current.x - origin.x;
90 let dy = current.y - origin.y;
91 dx * dx + dy * dy >= UNDOCK_THRESHOLD_PX * UNDOCK_THRESHOLD_PX
92}
93
94pub fn main_first_index(side: EdgeSide) -> bool {
95 matches!(side, EdgeSide::Right | EdgeSide::Bottom)
96}
97
98#[derive(Clone)]
99pub struct EdgeGhost {
100 side: EdgeSide,
101 color: Color,
102}
103
104impl EdgeGhost {
105 pub fn new(side: EdgeSide) -> Self {
106 Self {
107 side,
108
109 color: Color::rgb8(0xFF, 0xFF, 0xFF),
110 }
111 }
112
113 pub fn color(mut self, color: Color) -> Self {
114 self.color = color;
115 self
116 }
117}
118
119impl View for EdgeGhost {
120 fn body(&self) -> impl View + 'static {
121 Unit
122 }
123
124 fn children_override(&self) -> Option<Vec<AnyView>> {
125 let line_color = self.color;
126 let bar: AnyView = match self.side {
127 EdgeSide::Left => AnyView::new(
128 Row::new()
129 .fill_size()
130 .child(
131 crate::views::Panel::new()
132 .background(line_color)
133 .width(EDGE_LINE_PX)
134 .fill_height(),
135 )
136 .child_flex(Unit, 1.0),
137 ),
138 EdgeSide::Right => AnyView::new(
139 Row::new().fill_size().child_flex(Unit, 1.0).child(
140 crate::views::Panel::new()
141 .background(line_color)
142 .width(EDGE_LINE_PX)
143 .fill_height(),
144 ),
145 ),
146 EdgeSide::Top => AnyView::new(
147 Column::new()
148 .fill_size()
149 .child(
150 crate::views::Panel::new()
151 .background(line_color)
152 .fill_width()
153 .height(EDGE_LINE_PX),
154 )
155 .child_flex(Unit, 1.0),
156 ),
157 EdgeSide::Bottom => AnyView::new(
158 Column::new().fill_size().child_flex(Unit, 1.0).child(
159 crate::views::Panel::new()
160 .background(line_color)
161 .fill_width()
162 .height(EDGE_LINE_PX),
163 ),
164 ),
165 };
166
167 Some(vec![bar])
168 }
169
170 fn layout_style(&self) -> LayoutStyle {
171 let wide = Length::Px(EDGE_GHOST_PX);
172 let full = Length::Fill(1.0);
173 let (w, h) = match self.side {
174 EdgeSide::Left | EdgeSide::Right => (wide, full),
175 EdgeSide::Top | EdgeSide::Bottom => (full, wide),
176 };
177 LayoutStyle {
178 width: w,
179 height: h,
180 ..LayoutStyle::default()
181 }
182 }
183
184 fn hit_testable(&self) -> bool {
185 false
186 }
187}
188
189pub struct EdgeSlab {
190 side: EdgeSide,
191 accepts: Rc<dyn Fn(&DragPayload) -> bool>,
192 on_over: Rc<dyn Fn()>,
193 on_drop: Rc<dyn Fn(DropPosition, &mut EventCtx)>,
194}
195
196impl Clone for EdgeSlab {
197 fn clone(&self) -> Self {
198 Self {
199 side: self.side,
200 accepts: self.accepts.clone(),
201 on_over: self.on_over.clone(),
202 on_drop: self.on_drop.clone(),
203 }
204 }
205}
206
207impl EdgeSlab {
208 pub fn new(side: EdgeSide) -> Self {
209 Self {
210 side,
211 accepts: Rc::new(|_| false),
212 on_over: Rc::new(|| {}),
213 on_drop: Rc::new(|_, _| {}),
214 }
215 }
216
217 pub fn get_side(&self) -> EdgeSide {
218 self.side
219 }
220
221 pub fn accepts(mut self, f: impl Fn(&DragPayload) -> bool + 'static) -> Self {
222 self.accepts = Rc::new(f);
223 self
224 }
225
226 pub fn on_drag_over(mut self, f: impl Fn() + 'static) -> Self {
227 self.on_over = Rc::new(f);
228 self
229 }
230
231 pub fn on_drop_with(mut self, f: impl Fn(DropPosition, &mut EventCtx) + 'static) -> Self {
232 self.on_drop = Rc::new(f);
233 self
234 }
235}
236
237impl View for EdgeSlab {
238 fn body(&self) -> impl View + 'static {
239 Unit
240 }
241
242 fn children_override(&self) -> Option<Vec<AnyView>> {
243 None
244 }
245
246 fn layout_style(&self) -> LayoutStyle {
247 let wide = Length::Px(EDGE_GHOST_PX);
248 let full = Length::Fill(1.0);
249 let (w, h) = match self.side {
250 EdgeSide::Left | EdgeSide::Right => (wide, full),
251 EdgeSide::Top | EdgeSide::Bottom => (full, wide),
252 };
253 LayoutStyle {
254 width: w,
255 height: h,
256 ..LayoutStyle::default()
257 }
258 }
259
260 fn accepts_drop(&self, payload: &DragPayload) -> bool {
261 (self.accepts)(payload)
262 }
263
264 fn on_drag_over(&self, _pos: DropPosition, _ctx: &mut EventCtx) {
265 (self.on_over)();
266 }
267
268 fn on_drop(&self, pos: DropPosition, ctx: &mut EventCtx) {
269 (self.on_drop)(pos, ctx);
270 }
271
272 fn paint_self(&self, _ctx: &mut PaintCtx) {}
273}
274
275#[allow(dead_code)]
276fn _assert_send_sync() {
277 fn is<T: Clone + 'static>() {}
278 is::<EdgeSide>();
279 is::<EdgeGhost>();
280}