1use crate::theme::MacTheme;
7use egui::{
8 Align, CornerRadius, CursorIcon, Id, Layout, Pos2, Rect, Stroke, StrokeKind, Ui, UiBuilder,
9 Vec2,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum MacSplitOrientation {
15 #[default]
17 Horizontal,
18 Vertical,
20}
21
22pub struct MacSplitView<'a> {
47 id: Id,
48 orientation: MacSplitOrientation,
49 pos_override: Option<&'a mut f32>,
50 theme: Option<&'a MacTheme>,
51 min_first: Option<f32>,
52 max_first: Option<f32>,
53 min_second: Option<f32>,
54 collapsed_first: bool,
55 collapsed_second: bool,
56 show_handle: bool,
57 default_ratio: Option<f32>,
58}
59
60impl<'a> MacSplitView<'a> {
61 pub fn new(
63 id_source: impl std::hash::Hash + std::fmt::Debug,
64 orientation: MacSplitOrientation,
65 ) -> Self {
66 #[allow(deprecated)]
67 let id = Id::new(id_source);
68 Self {
69 id,
70 orientation,
71 pos_override: None,
72 theme: None,
73 min_first: None,
74 max_first: None,
75 min_second: None,
76 collapsed_first: false,
77 collapsed_second: false,
78 show_handle: true,
79 default_ratio: None,
80 }
81 }
82
83 pub fn with_pos(
85 id_source: impl std::hash::Hash + std::fmt::Debug,
86 orientation: MacSplitOrientation,
87 pos: &'a mut f32,
88 ) -> Self {
89 let mut view = Self::new(id_source, orientation);
90 view.pos_override = Some(pos);
91 view
92 }
93
94 pub fn theme(mut self, theme: &'a MacTheme) -> Self {
96 self.theme = Some(theme);
97 self
98 }
99
100 pub fn min_first(mut self, min: f32) -> Self {
102 self.min_first = Some(min);
103 self
104 }
105
106 pub fn max_first(mut self, max: f32) -> Self {
108 self.max_first = Some(max);
109 self
110 }
111
112 pub fn min_second(mut self, min: f32) -> Self {
114 self.min_second = Some(min);
115 self
116 }
117
118 pub fn collapsed_first(mut self, collapsed: bool) -> Self {
120 self.collapsed_first = collapsed;
121 self
122 }
123
124 pub fn collapsed_second(mut self, collapsed: bool) -> Self {
126 self.collapsed_second = collapsed;
127 self
128 }
129
130 pub fn show_handle(mut self, show: bool) -> Self {
132 self.show_handle = show;
133 self
134 }
135
136 pub fn default_ratio(mut self, ratio: f32) -> Self {
138 self.default_ratio = Some(ratio.clamp(0.0, 1.0));
139 self
140 }
141
142 pub fn split_panes(&mut self, ui: &mut Ui) -> (Rect, Rect) {
145 let fallback_theme;
146 let theme = match self.theme {
147 Some(t) => t,
148 None => {
149 fallback_theme = MacTheme::default();
150 &fallback_theme
151 }
152 };
153
154 let metrics = theme.split_view();
155 let divider_thickness = metrics.divider_thickness.points();
156 let hit_thickness = metrics.hit_thickness.points();
157 let min_pane_size = metrics.min_pane_size.points();
158 let handle_len = metrics.handle_length.points();
159 let handle_th = metrics.handle_thickness.points();
160
161 let container_rect = ui.available_rect_before_wrap();
162 let total_span = match self.orientation {
163 MacSplitOrientation::Horizontal => container_rect.width(),
164 MacSplitOrientation::Vertical => container_rect.height(),
165 };
166
167 let default_ratio = self
168 .default_ratio
169 .unwrap_or_else(|| metrics.default_ratio.points());
170 let mut pos = if let Some(ref p) = self.pos_override {
172 **p
173 } else {
174 ui.data_mut(|d| *d.get_temp_mut_or_insert_with(self.id, || total_span * default_ratio))
175 };
176
177 let min_1 = self.min_first.unwrap_or(min_pane_size);
178 let min_2 = self.min_second.unwrap_or(min_pane_size);
179 let max_1 = self
180 .max_first
181 .unwrap_or(total_span - min_2)
182 .min(total_span - min_2);
183 pos = pos.clamp(min_1, max_1.max(min_1));
184
185 let (first_rect, second_rect, divider_line, handle_rect) = if self.collapsed_first {
186 let r2 = container_rect;
187 let r1 = Rect::from_min_size(container_rect.min, Vec2::ZERO);
188 (r1, r2, None, None)
189 } else if self.collapsed_second {
190 let r1 = container_rect;
191 let r2 = Rect::from_min_size(container_rect.max, Vec2::ZERO);
192 (r1, r2, None, None)
193 } else {
194 match self.orientation {
195 MacSplitOrientation::Horizontal => {
196 let hit_rect = Rect::from_min_max(
197 Pos2::new(
198 container_rect.left() + pos - hit_thickness * 0.5,
199 container_rect.top(),
200 ),
201 Pos2::new(
202 container_rect.left() + pos + hit_thickness * 0.5,
203 container_rect.bottom(),
204 ),
205 );
206 let hit_resp = ui.interact(
207 hit_rect,
208 self.id.with("split_drag"),
209 egui::Sense::click_and_drag(),
210 );
211 if hit_resp.hovered() || hit_resp.dragged() {
212 ui.output_mut(|o| o.cursor_icon = CursorIcon::ResizeHorizontal);
213 }
214 if hit_resp.dragged() {
215 pos += hit_resp.drag_delta().x;
216 pos = pos.clamp(min_1, max_1.max(min_1));
217 }
218
219 let split_x = container_rect.left() + pos;
220 let divider_center_x = split_x + divider_thickness * 0.5;
221 let r1 = Rect::from_min_max(
222 container_rect.min,
223 Pos2::new(split_x.min(container_rect.right()), container_rect.bottom()),
224 );
225 let r2_left = (split_x + divider_thickness).min(container_rect.right());
226 let r2 = Rect::from_min_max(
227 Pos2::new(r2_left, container_rect.top()),
228 container_rect.max,
229 );
230
231 let line = (
232 Pos2::new(divider_center_x, container_rect.top()),
233 Pos2::new(divider_center_x, container_rect.bottom()),
234 );
235 let h_rect = if self.show_handle {
236 let center_y = container_rect.center().y;
237 Some(Rect::from_center_size(
238 Pos2::new(divider_center_x, center_y),
239 Vec2::new(handle_th, handle_len),
240 ))
241 } else {
242 None
243 };
244
245 (r1, r2, Some(line), h_rect)
246 }
247 MacSplitOrientation::Vertical => {
248 let hit_rect = Rect::from_min_max(
249 Pos2::new(
250 container_rect.left(),
251 container_rect.top() + pos - hit_thickness * 0.5,
252 ),
253 Pos2::new(
254 container_rect.right(),
255 container_rect.top() + pos + hit_thickness * 0.5,
256 ),
257 );
258 let hit_resp = ui.interact(
259 hit_rect,
260 self.id.with("split_drag"),
261 egui::Sense::click_and_drag(),
262 );
263 if hit_resp.hovered() || hit_resp.dragged() {
264 ui.output_mut(|o| o.cursor_icon = CursorIcon::ResizeVertical);
265 }
266 if hit_resp.dragged() {
267 pos += hit_resp.drag_delta().y;
268 pos = pos.clamp(min_1, max_1.max(min_1));
269 }
270
271 let split_y = container_rect.top() + pos;
272 let divider_center_y = split_y + divider_thickness * 0.5;
273 let r1 = Rect::from_min_max(
274 container_rect.min,
275 Pos2::new(container_rect.right(), split_y.min(container_rect.bottom())),
276 );
277 let r2_top = (split_y + divider_thickness).min(container_rect.bottom());
278 let r2 = Rect::from_min_max(
279 Pos2::new(container_rect.left(), r2_top),
280 container_rect.max,
281 );
282
283 let line = (
284 Pos2::new(container_rect.left(), divider_center_y),
285 Pos2::new(container_rect.right(), divider_center_y),
286 );
287 let h_rect = if self.show_handle {
288 let center_x = container_rect.center().x;
289 Some(Rect::from_center_size(
290 Pos2::new(center_x, divider_center_y),
291 Vec2::new(handle_len, handle_th),
292 ))
293 } else {
294 None
295 };
296
297 (r1, r2, Some(line), h_rect)
298 }
299 }
300 };
301
302 if let Some(ref mut p) = self.pos_override {
304 **p = pos;
305 } else {
306 ui.data_mut(|d| d.insert_temp(self.id, pos));
307 }
308
309 let painter = ui.painter_at(container_rect);
311 if let Some((p1, p2)) = divider_line {
312 painter.line_segment([p1, p2], Stroke::new(divider_thickness, theme.separator()));
313 }
314 if let Some(hr) = handle_rect {
315 painter.rect(
316 hr,
317 CornerRadius::same((handle_th * 0.5) as u8),
318 theme.control(),
319 Stroke::NONE,
320 StrokeKind::Inside,
321 );
322 }
323
324 (first_rect, second_rect)
325 }
326
327 pub fn show<R1, R2>(
329 mut self,
330 ui: &mut Ui,
331 first: impl FnOnce(&mut Ui) -> R1,
332 second: impl FnOnce(&mut Ui) -> R2,
333 ) -> (R1, R2) {
334 let container_rect = ui.available_rect_before_wrap();
335 let (first_rect, second_rect) = self.split_panes(ui);
336
337 let mut ui1 = ui.new_child(
339 UiBuilder::new()
340 .max_rect(first_rect)
341 .layout(Layout::top_down(Align::Min)),
342 );
343 let r1 = first(&mut ui1);
344
345 let mut ui2 = ui.new_child(
346 UiBuilder::new()
347 .max_rect(second_rect)
348 .layout(Layout::top_down(Align::Min)),
349 );
350 let r2 = second(&mut ui2);
351
352 ui.advance_cursor_after_rect(container_rect);
353
354 (r1, r2)
355 }
356}