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 corner_radius,
343 } = get_theme!(&self.theme, resizable_handle);
344 let mut size = use_state(Area::default);
345 let mut clicking = use_state(|| false);
346 let mut status = use_state(HandleStatus::default);
347 let mut registry = use_consume::<State<ResizableContext>>();
348 let container_size = use_consume::<State<Area>>();
349 let mut allow_resizing = use_state(|| false);
350
351 let panel_index = self.panel_index;
352
353 use_drop(move || {
354 if *status.peek() == HandleStatus::Hovering {
355 Cursor::set(CursorIcon::default());
356 }
357 });
358
359 let cursor = match registry.read().direction {
360 Direction::Horizontal => CursorIcon::ColResize,
361 _ => CursorIcon::RowResize,
362 };
363
364 let on_pointer_leave = move |_| {
365 *status.write() = HandleStatus::Idle;
366 if !clicking() {
367 Cursor::set(CursorIcon::default());
368 }
369 };
370
371 let on_pointer_enter = move |_| {
372 *status.write() = HandleStatus::Hovering;
373 Cursor::set(cursor);
374 };
375
376 let on_capture_global_mouse_move = move |e: Event<MouseEventData>| {
377 if *clicking.read() {
378 e.prevent_default();
379
380 if !*allow_resizing.read() {
381 return;
382 }
383
384 let coordinates = e.global_location;
385 let mut registry = registry.write();
386
387 let total_size = registry.panels.iter().fold(0., |acc, p| acc + p.size);
388
389 let distance = match registry.direction {
390 Direction::Horizontal => {
391 let container_width = container_size.read().width();
392 let displacement = coordinates.x as f32 - size.read().min_x();
393 total_size / container_width * displacement
394 }
395 Direction::Vertical => {
396 let container_height = container_size.read().height();
397 let displacement = coordinates.y as f32 - size.read().min_y();
398 total_size / container_height * displacement
399 }
400 };
401
402 let changed_panels = registry.apply_resize(panel_index, distance);
403
404 if changed_panels {
405 allow_resizing.set(false);
406 }
407 }
408 };
409
410 let on_pointer_down = move |e: Event<PointerEventData>| {
411 e.stop_propagation();
412 e.prevent_default();
413 clicking.set(true);
414 };
415
416 let on_global_mouse_up = move |_| {
417 if *clicking.read() {
418 if *status.peek() != HandleStatus::Hovering {
419 Cursor::set(CursorIcon::default());
420 }
421 clicking.set(false);
422 }
423 };
424
425 let (width, height) = match registry.read().direction {
426 Direction::Horizontal => (Size::px(4.), Size::fill()),
427 Direction::Vertical => (Size::fill(), Size::px(4.)),
428 };
429
430 let background = match *status.read() {
431 _ if *clicking.read() => hover_background,
432 HandleStatus::Hovering => hover_background,
433 HandleStatus::Idle => background,
434 };
435
436 rect()
437 .width(width)
438 .height(height)
439 .background(background)
440 .corner_radius(corner_radius)
441 .on_sized(move |e: Event<SizedEventData>| {
442 size.set(e.area);
443 allow_resizing.set(true);
444 })
445 .on_pointer_down(on_pointer_down)
446 .on_global_mouse_up(on_global_mouse_up)
447 .on_pointer_enter(on_pointer_enter)
448 .on_capture_global_mouse_move(on_capture_global_mouse_move)
449 .on_pointer_leave(on_pointer_leave)
450 }
451}