1use freya_core::prelude::*;
2use thiserror::Error;
3use torin::{
4 content::Content,
5 prelude::{
6 Area,
7 Direction,
8 },
9 size::Size,
10};
11
12use crate::{
13 get_theme,
14 theming::component_themes::{
15 ResizableHandleTheme,
16 ResizableHandleThemePartial,
17 },
18};
19
20#[derive(Error, Debug)]
21pub enum ResizableError {
22 #[error("Panel does not exist")]
23 PanelNotFound,
24}
25
26#[derive(Clone, Copy, Debug)]
27pub struct Panel {
28 pub size: f32,
29 pub initial_size: f32,
30 pub min_size: f32,
31 pub id: usize,
32}
33
34#[derive(Default)]
35pub struct ResizableContext {
36 pub panels: Vec<Panel>,
37 pub direction: Direction,
38}
39
40impl ResizableContext {
41 pub fn direction(&self) -> Direction {
42 self.direction
43 }
44
45 pub fn panels(&mut self) -> &mut Vec<Panel> {
46 &mut self.panels
47 }
48
49 pub fn push_panel(&mut self, panel: Panel, order: Option<usize>) {
50 let mut buffer = panel.size;
51
52 for panel in &mut self.panels.iter_mut() {
53 let resized_sized = (panel.initial_size - panel.size).min(buffer);
54
55 if resized_sized >= 0. {
56 panel.size = (panel.size - resized_sized).max(panel.min_size);
57 let new_resized_sized = panel.initial_size - panel.size;
58 buffer -= new_resized_sized;
59 }
60 }
61
62 if let Some(order) = order {
63 if self.panels.len() <= order {
64 self.panels.push(panel);
65 } else {
66 self.panels.insert(order, panel);
67 }
68 } else {
69 self.panels.push(panel);
70 }
71 }
72
73 pub fn remove_panel(&mut self, id: usize) -> Result<(), ResizableError> {
74 let removed_panel = self
75 .panels
76 .iter()
77 .find(|p| p.id == id)
78 .cloned()
79 .ok_or(ResizableError::PanelNotFound)?;
80 self.panels.retain(|e| e.id != id);
81
82 let mut buffer = removed_panel.size;
83
84 for panel in &mut self.panels.iter_mut() {
85 let resized_sized = (panel.initial_size - panel.size).min(buffer);
86
87 panel.size = (panel.size + resized_sized).max(panel.min_size);
88 let new_resized_sized = panel.initial_size - panel.size;
89 buffer -= new_resized_sized;
90 }
91
92 Ok(())
93 }
94
95 pub fn apply_resize(&mut self, panel_index: usize, distance: f32) -> bool {
96 let mut changed_panels = false;
97
98 let (corrected_distance, behind_range, forward_range) = if distance >= 0. {
99 (distance, 0..panel_index, panel_index..self.panels.len())
100 } else {
101 (-distance, panel_index..self.panels.len(), 0..panel_index)
102 };
103
104 let mut acc_per = 0.0;
105
106 for panel in &mut self.panels[forward_range].iter_mut() {
108 let old_size = panel.size;
109 let new_size = (panel.size - corrected_distance).clamp(panel.min_size, 100.);
110
111 if panel.size != new_size {
112 changed_panels = true
113 }
114
115 panel.size = new_size;
116 acc_per -= new_size - old_size;
117
118 if old_size > panel.min_size {
119 break;
120 }
121 }
122
123 if let Some(panel) = &mut self.panels[behind_range].iter_mut().next_back() {
125 let new_size = (panel.size + acc_per).clamp(panel.min_size, 100.);
126
127 if panel.size != new_size {
128 changed_panels = true
129 }
130
131 panel.size = new_size;
132 }
133
134 changed_panels
135 }
136}
137
138#[derive(PartialEq)]
139pub struct ResizableContainer {
140 direction: Direction,
143 panels: Vec<ResizablePanel>,
145}
146
147impl Default for ResizableContainer {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153impl ResizableContainer {
154 pub fn new() -> Self {
155 Self {
156 direction: Direction::Vertical,
157 panels: vec![],
158 }
159 }
160
161 pub fn direction(mut self, direction: Direction) -> Self {
162 self.direction = direction;
163 self
164 }
165
166 pub fn panel(mut self, panel: impl Into<Option<ResizablePanel>>) -> Self {
167 if let Some(panel) = panel.into() {
168 self.panels.push(panel);
169 }
170
171 self
172 }
173
174 pub fn panels_iter(mut self, panels: impl Iterator<Item = ResizablePanel>) -> Self {
175 self.panels.extend(panels);
176
177 self
178 }
179}
180
181impl Render for ResizableContainer {
182 fn render(&self) -> impl IntoElement {
183 let mut size = use_state(Area::default);
184 use_provide_context(|| size);
185
186 use_provide_context(|| {
187 State::create(ResizableContext {
188 direction: self.direction,
189 ..Default::default()
190 })
191 });
192
193 rect()
194 .direction(self.direction)
195 .on_sized(move |e: Event<SizedEventData>| size.set(e.area))
196 .expanded()
197 .content(Content::flex())
198 .children_iter(self.panels.iter().enumerate().flat_map(|(i, e)| {
199 if i > 0 {
200 vec![ResizableHandle::new(i).into(), e.clone().into()]
201 } else {
202 vec![e.clone().into()]
203 }
204 }))
205 }
206}
207
208#[derive(PartialEq, Clone)]
209pub struct ResizablePanel {
210 key: DiffKey,
211 initial_size: f32,
212 min_size: Option<f32>,
213 children: Vec<Element>,
214 order: Option<usize>,
215}
216
217impl KeyExt for ResizablePanel {
218 fn write_key(&mut self) -> &mut DiffKey {
219 &mut self.key
220 }
221}
222
223impl ChildrenExt for ResizablePanel {
224 fn get_children(&mut self) -> &mut Vec<Element> {
225 &mut self.children
226 }
227}
228
229impl ResizablePanel {
230 pub fn new(initial_size: f32) -> Self {
231 Self {
232 key: DiffKey::None,
233 initial_size,
234 min_size: None,
235 children: vec![],
236 order: None,
237 }
238 }
239
240 pub fn key(mut self, key: impl Into<DiffKey>) -> Self {
241 self.key = key.into();
242 self
243 }
244
245 pub fn initial_size(mut self, initial_size: impl Into<f32>) -> Self {
246 self.initial_size = initial_size.into();
247 self
248 }
249
250 pub fn min_size(mut self, min_size: impl Into<f32>) -> Self {
251 self.min_size = Some(min_size.into());
252 self
253 }
254
255 pub fn order(mut self, order: impl Into<usize>) -> Self {
256 self.order = Some(order.into());
257 self
258 }
259}
260
261impl Render for ResizablePanel {
262 fn render(&self) -> impl IntoElement {
263 let mut registry = use_consume::<State<ResizableContext>>();
264
265 let id = use_hook(|| {
266 let id = UseId::<ResizableContext>::get_in_hook();
267
268 let panel = Panel {
269 initial_size: self.initial_size,
270 size: self.initial_size,
271 min_size: self.min_size.unwrap_or(self.initial_size * 0.25),
272 id,
273 };
274
275 registry.write().push_panel(panel, self.order);
276
277 id
278 });
279
280 use_drop(move || {
281 let _ = registry.write().remove_panel(id);
283 });
284
285 let registry = registry.read();
286 let index = registry
287 .panels
288 .iter()
289 .position(|e| e.id == id)
290 .unwrap_or_default();
291
292 let Panel { size, .. } = registry.panels[index];
293
294 let (width, height) = match registry.direction {
295 Direction::Horizontal => (Size::flex(size), Size::fill()),
296 Direction::Vertical => (Size::fill(), Size::flex(size)),
297 };
298
299 rect()
300 .width(width)
301 .height(height)
302 .overflow(Overflow::Clip)
303 .children(self.children.clone())
304 }
305
306 fn render_key(&self) -> DiffKey {
307 self.key.clone().or(DiffKey::None)
308 }
309}
310
311#[derive(Debug, Default, PartialEq, Clone, Copy)]
313pub enum HandleStatus {
314 #[default]
316 Idle,
317 Hovering,
319}
320
321#[derive(PartialEq)]
322pub struct ResizableHandle {
323 panel_index: usize,
324 pub(crate) theme: Option<ResizableHandleThemePartial>,
326}
327
328impl ResizableHandle {
329 pub fn new(panel_index: usize) -> Self {
330 Self {
331 panel_index,
332 theme: None,
333 }
334 }
335}
336
337impl Render for ResizableHandle {
338 fn render(&self) -> impl IntoElement {
339 let ResizableHandleTheme {
340 background,
341 hover_background,
342 } = get_theme!(&self.theme, resizable_handle);
343 let mut size = use_state(Area::default);
344 let mut clicking = use_state(|| false);
345 let mut status = use_state(HandleStatus::default);
346 let mut registry = use_consume::<State<ResizableContext>>();
347 let container_size = use_consume::<State<Area>>();
348 let mut allow_resizing = use_state(|| false);
349
350 let panel_index = self.panel_index;
351
352 use_drop(move || {
353 if *status.peek() == HandleStatus::Hovering {
354 Cursor::set(CursorIcon::default());
355 }
356 });
357
358 let cursor = match registry.read().direction {
359 Direction::Horizontal => CursorIcon::ColResize,
360 _ => CursorIcon::RowResize,
361 };
362
363 let on_pointer_leave = move |_| {
364 *status.write() = HandleStatus::Idle;
365 if !clicking() {
366 Cursor::set(CursorIcon::default());
367 }
368 };
369
370 let on_pointer_enter = move |_| {
371 *status.write() = HandleStatus::Hovering;
372 Cursor::set(cursor);
373 };
374
375 let on_capture_global_mouse_move = move |e: Event<MouseEventData>| {
376 if *clicking.read() {
377 e.prevent_default();
378
379 if !*allow_resizing.read() {
380 return;
381 }
382
383 let coordinates = e.global_location;
384 let mut registry = registry.write();
385
386 let total_size = registry.panels.iter().fold(0., |acc, p| acc + p.size);
387
388 let distance = match registry.direction {
389 Direction::Horizontal => {
390 let container_width = container_size.read().width();
391 let displacement = coordinates.x as f32 - size.read().min_x();
392 total_size / container_width * displacement
393 }
394 Direction::Vertical => {
395 let container_height = container_size.read().height();
396 let displacement = coordinates.y as f32 - size.read().min_y();
397 total_size / container_height * displacement
398 }
399 };
400
401 let changed_panels = registry.apply_resize(panel_index, distance);
402
403 if changed_panels {
404 allow_resizing.set(false);
405 }
406 }
407 };
408
409 let on_pointer_down = move |e: Event<PointerEventData>| {
410 e.stop_propagation();
411 e.prevent_default();
412 clicking.set(true);
413 };
414
415 let on_global_mouse_up = move |_| {
416 if *clicking.read() {
417 if *status.peek() != HandleStatus::Hovering {
418 Cursor::set(CursorIcon::default());
419 }
420 clicking.set(false);
421 }
422 };
423
424 let (width, height) = match registry.read().direction {
425 Direction::Horizontal => (Size::px(4.), Size::fill()),
426 Direction::Vertical => (Size::fill(), Size::px(4.)),
427 };
428
429 let background = match *status.read() {
430 _ if *clicking.read() => hover_background,
431 HandleStatus::Hovering => hover_background,
432 HandleStatus::Idle => background,
433 };
434
435 rect()
436 .width(width)
437 .height(height)
438 .background(background)
439 .on_sized(move |e: Event<SizedEventData>| {
440 size.set(e.area);
441 allow_resizing.set(true);
442 })
443 .on_pointer_down(on_pointer_down)
444 .on_global_mouse_up(on_global_mouse_up)
445 .on_pointer_enter(on_pointer_enter)
446 .on_capture_global_mouse_move(on_capture_global_mouse_move)
447 .on_pointer_leave(on_pointer_leave)
448 }
449}