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#[cfg_attr(feature = "docs",
162 doc = embed_doc_image::embed_image!("resizable_container", "images/gallery_resizable_container.png"),
163)]
164#[derive(PartialEq)]
165pub struct ResizableContainer {
166 direction: Direction,
169 panels: Vec<ResizablePanel>,
171}
172
173impl Default for ResizableContainer {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179impl ResizableContainer {
180 pub fn new() -> Self {
181 Self {
182 direction: Direction::Vertical,
183 panels: vec![],
184 }
185 }
186
187 pub fn direction(mut self, direction: Direction) -> Self {
188 self.direction = direction;
189 self
190 }
191
192 pub fn panel(mut self, panel: impl Into<Option<ResizablePanel>>) -> Self {
193 if let Some(panel) = panel.into() {
194 self.panels.push(panel);
195 }
196
197 self
198 }
199
200 pub fn panels_iter(mut self, panels: impl Iterator<Item = ResizablePanel>) -> Self {
201 self.panels.extend(panels);
202
203 self
204 }
205}
206
207impl Component for ResizableContainer {
208 fn render(&self) -> impl IntoElement {
209 let mut size = use_state(Area::default);
210 use_provide_context(|| size);
211
212 use_provide_context(|| {
213 State::create(ResizableContext {
214 direction: self.direction,
215 ..Default::default()
216 })
217 });
218
219 rect()
220 .direction(self.direction)
221 .on_sized(move |e: Event<SizedEventData>| size.set(e.area))
222 .expanded()
223 .content(Content::flex())
224 .children(self.panels.iter().enumerate().flat_map(|(i, e)| {
225 if i > 0 {
226 vec![ResizableHandle::new(i).into(), e.clone().into()]
227 } else {
228 vec![e.clone().into()]
229 }
230 }))
231 }
232}
233
234#[derive(PartialEq, Clone)]
235pub struct ResizablePanel {
236 key: DiffKey,
237 initial_size: f32,
238 min_size: Option<f32>,
239 children: Vec<Element>,
240 order: Option<usize>,
241}
242
243impl KeyExt for ResizablePanel {
244 fn write_key(&mut self) -> &mut DiffKey {
245 &mut self.key
246 }
247}
248
249impl ChildrenExt for ResizablePanel {
250 fn get_children(&mut self) -> &mut Vec<Element> {
251 &mut self.children
252 }
253}
254
255impl ResizablePanel {
256 pub fn new(initial_size: f32) -> Self {
257 Self {
258 key: DiffKey::None,
259 initial_size,
260 min_size: None,
261 children: vec![],
262 order: None,
263 }
264 }
265
266 pub fn key(mut self, key: impl Into<DiffKey>) -> Self {
267 self.key = key.into();
268 self
269 }
270
271 pub fn initial_size(mut self, initial_size: impl Into<f32>) -> Self {
272 self.initial_size = initial_size.into();
273 self
274 }
275
276 pub fn min_size(mut self, min_size: impl Into<f32>) -> Self {
277 self.min_size = Some(min_size.into());
278 self
279 }
280
281 pub fn order(mut self, order: impl Into<usize>) -> Self {
282 self.order = Some(order.into());
283 self
284 }
285}
286
287impl Component for ResizablePanel {
288 fn render(&self) -> impl IntoElement {
289 let mut registry = use_consume::<State<ResizableContext>>();
290
291 let id = use_hook(|| {
292 let id = UseId::<ResizableContext>::get_in_hook();
293
294 let panel = Panel {
295 initial_size: self.initial_size,
296 size: self.initial_size,
297 min_size: self.min_size.unwrap_or(self.initial_size * 0.25),
298 id,
299 };
300
301 registry.write().push_panel(panel, self.order);
302
303 id
304 });
305
306 use_drop(move || {
307 let _ = registry.write().remove_panel(id);
309 });
310
311 let registry = registry.read();
312 let index = registry
313 .panels
314 .iter()
315 .position(|e| e.id == id)
316 .unwrap_or_default();
317
318 let Panel { size, .. } = registry.panels[index];
319
320 let (width, height) = match registry.direction {
321 Direction::Horizontal => (Size::flex(size), Size::fill()),
322 Direction::Vertical => (Size::fill(), Size::flex(size)),
323 };
324
325 rect()
326 .a11y_role(AccessibilityRole::Pane)
327 .width(width)
328 .height(height)
329 .overflow(Overflow::Clip)
330 .children(self.children.clone())
331 }
332
333 fn render_key(&self) -> DiffKey {
334 self.key.clone().or(DiffKey::None)
335 }
336}
337
338#[derive(Debug, Default, PartialEq, Clone, Copy)]
340pub enum HandleStatus {
341 #[default]
343 Idle,
344 Hovering,
346}
347
348#[derive(PartialEq)]
349pub struct ResizableHandle {
350 panel_index: usize,
351 pub(crate) theme: Option<ResizableHandleThemePartial>,
353}
354
355impl ResizableHandle {
356 pub fn new(panel_index: usize) -> Self {
357 Self {
358 panel_index,
359 theme: None,
360 }
361 }
362}
363
364impl Component for ResizableHandle {
365 fn render(&self) -> impl IntoElement {
366 let ResizableHandleTheme {
367 background,
368 hover_background,
369 corner_radius,
370 } = get_theme!(&self.theme, resizable_handle);
371 let mut size = use_state(Area::default);
372 let mut clicking = use_state(|| false);
373 let mut status = use_state(HandleStatus::default);
374 let mut registry = use_consume::<State<ResizableContext>>();
375 let container_size = use_consume::<State<Area>>();
376 let mut allow_resizing = use_state(|| false);
377
378 let panel_index = self.panel_index;
379
380 use_drop(move || {
381 if *status.peek() == HandleStatus::Hovering {
382 Cursor::set(CursorIcon::default());
383 }
384 });
385
386 let cursor = match registry.read().direction {
387 Direction::Horizontal => CursorIcon::ColResize,
388 _ => CursorIcon::RowResize,
389 };
390
391 let on_pointer_leave = move |_| {
392 *status.write() = HandleStatus::Idle;
393 if !clicking() {
394 Cursor::set(CursorIcon::default());
395 }
396 };
397
398 let on_pointer_enter = move |_| {
399 *status.write() = HandleStatus::Hovering;
400 Cursor::set(cursor);
401 };
402
403 let on_capture_global_mouse_move = move |e: Event<MouseEventData>| {
404 if *clicking.read() {
405 e.prevent_default();
406
407 if !*allow_resizing.read() {
408 return;
409 }
410
411 let coordinates = e.global_location;
412 let mut registry = registry.write();
413
414 let total_size = registry.panels.iter().fold(0., |acc, p| acc + p.size);
415
416 let distance = match registry.direction {
417 Direction::Horizontal => {
418 let container_width = container_size.read().width();
419 let displacement = coordinates.x as f32 - size.read().min_x();
420 total_size / container_width * displacement
421 }
422 Direction::Vertical => {
423 let container_height = container_size.read().height();
424 let displacement = coordinates.y as f32 - size.read().min_y();
425 total_size / container_height * displacement
426 }
427 };
428
429 let changed_panels = registry.apply_resize(panel_index, distance);
430
431 if changed_panels {
432 allow_resizing.set(false);
433 }
434 }
435 };
436
437 let on_pointer_down = move |e: Event<PointerEventData>| {
438 e.stop_propagation();
439 e.prevent_default();
440 clicking.set(true);
441 };
442
443 let on_global_mouse_up = move |_| {
444 if *clicking.read() {
445 if *status.peek() != HandleStatus::Hovering {
446 Cursor::set(CursorIcon::default());
447 }
448 clicking.set(false);
449 }
450 };
451
452 let (width, height) = match registry.read().direction {
453 Direction::Horizontal => (Size::px(4.), Size::fill()),
454 Direction::Vertical => (Size::fill(), Size::px(4.)),
455 };
456
457 let background = match *status.read() {
458 _ if *clicking.read() => hover_background,
459 HandleStatus::Hovering => hover_background,
460 HandleStatus::Idle => background,
461 };
462
463 rect()
464 .width(width)
465 .height(height)
466 .background(background)
467 .corner_radius(corner_radius)
468 .on_sized(move |e: Event<SizedEventData>| {
469 size.set(e.area);
470 allow_resizing.set(true);
471 })
472 .on_pointer_down(on_pointer_down)
473 .on_global_mouse_up(on_global_mouse_up)
474 .on_pointer_enter(on_pointer_enter)
475 .on_capture_global_mouse_move(on_capture_global_mouse_move)
476 .on_pointer_leave(on_pointer_leave)
477 }
478}