1use freya_core::prelude::*;
2use torin::{
3 content::Content,
4 node::Node,
5 prelude::{
6 Area,
7 Direction,
8 Length,
9 },
10 size::Size,
11};
12
13use crate::{
14 define_theme,
15 get_theme,
16};
17
18define_theme! {
19 %[component]
20 pub ResizableHandle {
21 %[fields]
22 background: Color,
23 hover_background: Color,
24 corner_radius: CornerRadius,
25 }
26}
27
28#[derive(PartialEq, Clone, Copy, Debug)]
30pub enum PanelSize {
31 Pixels(Length),
33 Percentage(Length),
35}
36
37impl PanelSize {
38 pub fn px(v: f32) -> Self {
39 Self::Pixels(Length::new(v))
40 }
41
42 pub fn percent(v: f32) -> Self {
43 Self::Percentage(Length::new(v))
44 }
45
46 pub fn value(&self) -> f32 {
47 match self {
48 Self::Pixels(v) | Self::Percentage(v) => v.get(),
49 }
50 }
51
52 fn max_size(&self) -> f32 {
54 match self {
55 Self::Pixels(_) => f32::MAX,
56 Self::Percentage(_) => 100.,
57 }
58 }
59
60 fn flex_scale(&self, flex_factor: f32) -> f32 {
62 match self {
63 Self::Pixels(_) => 1.0,
64 Self::Percentage(_) => flex_factor,
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug)]
71pub struct Panel {
72 pub size: f32,
73 pub initial_size: f32,
74 pub min_size: f32,
75 pub sizing: PanelSize,
76}
77
78impl Panel {
79 fn redistribute<'a>(&self, panels: impl Iterator<Item = &'a mut Panel>, adding: bool) {
82 if matches!(self.sizing, PanelSize::Pixels(_)) {
83 return;
84 }
85
86 let mut buffer = self.size;
87 for panel in panels.filter(|panel| matches!(panel.sizing, PanelSize::Percentage(_))) {
88 let adjustment = (panel.initial_size - panel.size).min(buffer);
89 if !adding || adjustment >= 0. {
90 let adjustment = if adding { -adjustment } else { adjustment };
91 panel.size = (panel.size + adjustment).max(panel.min_size);
92 buffer -= panel.initial_size - panel.size;
93 }
94 }
95 }
96}
97
98#[derive(Default)]
100pub struct ResizableContext {
101 panels: Vec<(DiffKey, Panel)>,
102 direction: Direction,
103 dragging: Option<usize>,
104}
105
106impl ResizableContext {
107 pub const HANDLE_SIZE: f32 = 4.0;
108
109 pub fn direction(&self) -> Direction {
110 self.direction
111 }
112
113 pub fn panels(&self) -> Vec<Panel> {
115 self.panels.iter().map(|(_, panel)| *panel).collect()
116 }
117
118 fn reconcile(&mut self, mut panels: Vec<(DiffKey, Panel)>, direction: Direction) -> bool {
119 let unchanged = self.panels.len() == panels.len()
120 && self
121 .panels
122 .iter()
123 .zip(&panels)
124 .all(|((old_key, old), (key, new))| {
125 old_key == key && old.sizing == new.sizing && old.min_size == new.min_size
126 });
127 if unchanged && self.direction == direction {
128 return false;
129 }
130
131 let mut previous = std::mem::take(&mut self.panels);
132 let mut index = 0;
133 while index < previous.len() {
134 if panels.iter().any(|(key, _)| *key == previous[index].0) {
135 index += 1;
136 } else {
137 let (_, removed) = previous.remove(index);
138 removed.redistribute(previous.iter_mut().map(|(_, panel)| panel), false);
139 }
140 }
141
142 for (key, panel) in &panels {
143 if !previous.iter().any(|(old_key, _)| old_key == key) {
144 panel.redistribute(previous.iter_mut().map(|(_, panel)| panel), true);
145 previous.push((key.clone(), *panel));
146 }
147 }
148
149 for (key, panel) in &mut panels {
150 if let Some((_, old)) = previous.iter().find(|(old_key, old)| {
151 old_key == key
152 && std::mem::discriminant(&old.sizing) == std::mem::discriminant(&panel.sizing)
153 }) {
154 panel.size = old.size;
155 }
156 panel.size = panel.size.clamp(panel.min_size, panel.sizing.max_size());
157 }
158 self.panels = panels;
159 self.direction = direction;
160 self.dragging = None;
161 true
162 }
163
164 pub fn apply_resize(
166 &mut self,
167 divider_index: usize,
168 drag_delta: f32,
169 container_size: f32,
170 ) -> bool {
171 if divider_index == 0 || divider_index >= self.panels.len() {
172 return false;
173 }
174
175 let panels = &mut self.panels;
176 let mut sizes_changed = false;
177
178 let handle_space = panels.len().saturating_sub(1) as f32 * Self::HANDLE_SIZE;
179 let (total_fixed_size, total_flex_weight) = panels.iter().fold(
180 (0.0, 0.0),
181 |(fixed_size, flex_weight): (f32, f32), (_, panel)| match panel.sizing {
182 PanelSize::Pixels(_) => (fixed_size + panel.size, flex_weight),
183 PanelSize::Percentage(_) => (fixed_size, flex_weight + panel.size),
184 },
185 );
186 let flex_per_pixel =
187 total_flex_weight / (container_size - total_fixed_size - handle_space).max(1.0);
188
189 let shrink_amount = drag_delta.abs();
190 let (growing_index, mut shrinking_range) = if drag_delta >= 0. {
191 (divider_index - 1, divider_index..panels.len())
192 } else {
193 (divider_index, 0..divider_index)
194 };
195
196 let mut freed_space = 0.0;
197
198 while let Some(index) = if drag_delta >= 0. {
199 shrinking_range.next()
200 } else {
201 shrinking_range.next_back()
202 } {
203 let (_, panel) = &mut panels[index];
204 let previous_size = panel.size;
205 let units_per_pixel = panel.sizing.flex_scale(flex_per_pixel);
206 let new_size = (panel.size - shrink_amount * units_per_pixel)
207 .clamp(panel.min_size, panel.sizing.max_size());
208 sizes_changed |= panel.size != new_size;
209 panel.size = new_size;
210 freed_space -= (new_size - previous_size) / units_per_pixel.max(f32::MIN_POSITIVE);
211
212 if previous_size > panel.min_size {
213 break;
214 }
215 }
216
217 let (_, panel) = &mut panels[growing_index];
218 let units_per_pixel = panel.sizing.flex_scale(flex_per_pixel);
219 let new_size = (panel.size + freed_space * units_per_pixel)
220 .clamp(panel.min_size, panel.sizing.max_size());
221 sizes_changed |= panel.size != new_size;
222 panel.size = new_size;
223
224 sizes_changed
225 }
226
227 pub fn reset(&mut self) {
228 for (_, panel) in &mut self.panels {
229 panel.size = panel
230 .initial_size
231 .clamp(panel.min_size, panel.sizing.max_size());
232 }
233 }
234}
235
236#[derive(PartialEq, Clone)]
251pub struct ResizableContainer {
252 key: DiffKey,
253 layout: LayoutData,
254 panels: Vec<ResizablePanel>,
255 controller: Option<Writable<ResizableContext>>,
256}
257
258impl Default for ResizableContainer {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264impl LayoutExt for ResizableContainer {
265 fn layout_mut(&mut self) -> &mut LayoutData {
266 &mut self.layout
267 }
268}
269
270impl ContainerExt for ResizableContainer {}
271
272impl KeyExt for ResizableContainer {
273 fn key_mut(&mut self) -> &mut DiffKey {
274 &mut self.key
275 }
276}
277
278impl ResizableContainer {
279 pub fn new() -> Self {
280 Self {
281 key: DiffKey::None,
282 layout: Node {
283 width: Size::fill(),
284 height: Size::fill(),
285 content: Content::flex(),
286 ..Default::default()
287 }
288 .into(),
289 panels: vec![],
290 controller: None,
291 }
292 }
293
294 pub fn direction(mut self, direction: Direction) -> Self {
295 self.layout.direction = direction;
296 self
297 }
298
299 pub fn panel(mut self, panel: impl Into<Option<ResizablePanel>>) -> Self {
300 if let Some(panel) = panel.into() {
301 self.panels.push(panel);
302 }
303 self
304 }
305
306 pub fn panels_iter(mut self, panels: impl Iterator<Item = ResizablePanel>) -> Self {
307 self.panels.extend(panels);
308 self
309 }
310
311 pub fn controller(mut self, controller: impl Into<Writable<ResizableContext>>) -> Self {
312 self.controller = Some(controller.into());
313 self
314 }
315}
316
317impl Component for ResizableContainer {
318 fn render(&self) -> impl IntoElement {
319 let mut size = use_state(Area::default);
320 use_provide_context(|| size);
321
322 let mut controller = use_provide_context(|| {
323 self.controller
324 .clone()
325 .unwrap_or_else(|| State::create(ResizableContext::default()).into_writable())
326 });
327 let declarations: Vec<_> = self
328 .panels
329 .iter()
330 .enumerate()
331 .map(|(index, panel)| {
332 let initial_size = panel.initial_size.value();
333 (
334 panel.key.clone().or(DiffKey::DefaultU64(index as u64)),
335 Panel {
336 size: initial_size,
337 initial_size,
338 min_size: panel.min_size.unwrap_or(initial_size * 0.25),
339 sizing: panel.initial_size,
340 },
341 )
342 })
343 .collect();
344 controller.write_if(|mut context| context.reconcile(declarations, self.layout.direction));
345 let context = controller.read();
346
347 rect()
348 .layout(self.layout.clone())
349 .on_sized(move |e: Event<SizedEventData>| size.set(e.area))
350 .on_global_pointer_up({
351 let mut controller = controller.clone();
352 move |_: Event<PointerEventData>| {
353 controller.write_if(|mut context| context.dragging.take().is_some());
354 }
355 })
356 .children(
357 context
358 .panels
359 .iter()
360 .zip(&self.panels)
361 .enumerate()
362 .flat_map(|(index, ((key, state), panel))| {
363 let main_size = match state.sizing {
364 PanelSize::Pixels(_) => Size::px(state.size),
365 PanelSize::Percentage(_) => Size::flex(state.size),
366 };
367 let (width, height) = match context.direction {
368 Direction::Horizontal => (main_size, Size::fill()),
369 Direction::Vertical => (Size::fill(), main_size),
370 };
371
372 [
373 (index > 0)
374 .then(|| ResizableHandle::new(index).key(index).into_element()),
375 Some(
376 rect()
377 .key(key)
378 .a11y_role(AccessibilityRole::Pane)
379 .width(width)
380 .height(height)
381 .overflow(panel.overflow)
382 .children(panel.children.clone())
383 .into_element(),
384 ),
385 ]
386 .into_iter()
387 .flatten()
388 }),
389 )
390 }
391
392 fn render_key(&self) -> DiffKey {
393 self.key.clone().or(self.default_key())
394 }
395}
396
397#[derive(PartialEq, Clone)]
400pub struct ResizablePanel {
401 key: DiffKey,
402 initial_size: PanelSize,
403 min_size: Option<f32>,
404 children: Vec<Element>,
405 overflow: Overflow,
406}
407
408impl KeyExt for ResizablePanel {
409 fn key_mut(&mut self) -> &mut DiffKey {
410 &mut self.key
411 }
412}
413
414impl ChildrenExt for ResizablePanel {
415 fn children_mut(&mut self) -> &mut Vec<Element> {
416 &mut self.children
417 }
418}
419
420impl ResizablePanel {
421 pub fn new(initial_size: PanelSize) -> Self {
422 Self {
423 key: DiffKey::None,
424 initial_size,
425 min_size: None,
426 children: vec![],
427 overflow: Overflow::Clip,
428 }
429 }
430
431 pub fn initial_size(mut self, initial_size: PanelSize) -> Self {
434 self.initial_size = initial_size;
435 self
436 }
437
438 pub fn min_size(mut self, min_size: f32) -> Self {
440 self.min_size = Some(min_size);
441 self
442 }
443
444 pub fn overflow(mut self, overflow: impl Into<Overflow>) -> Self {
446 self.overflow = overflow.into();
447 self
448 }
449}
450
451#[derive(Debug, Default, PartialEq, Clone, Copy)]
453pub enum HandleStatus {
454 #[default]
456 Idle,
457 Hovering,
459}
460
461#[derive(PartialEq)]
462pub struct ResizableHandle {
463 key: DiffKey,
464 panel_index: usize,
465 pub(crate) theme: Option<ResizableHandleThemePartial>,
467}
468
469impl ResizableHandle {
470 pub fn new(panel_index: usize) -> Self {
471 Self {
472 key: DiffKey::None,
473 panel_index,
474 theme: None,
475 }
476 }
477}
478
479impl KeyExt for ResizableHandle {
480 fn key_mut(&mut self) -> &mut DiffKey {
481 &mut self.key
482 }
483}
484
485impl Component for ResizableHandle {
486 fn render(&self) -> impl IntoElement {
487 let ResizableHandleTheme {
488 background,
489 hover_background,
490 corner_radius,
491 } = get_theme!(
492 &self.theme,
493 ResizableHandleThemePreference,
494 "resizable_handle"
495 );
496 let mut size = use_state(Area::default);
497 let mut status = use_state(HandleStatus::default);
498 let mut controller = use_consume::<Writable<ResizableContext>>();
499 let container_size = use_consume::<State<Area>>();
500 let mut allow_resizing = use_state(|| false);
501
502 let panel_index = self.panel_index;
503 let (direction, dragging) = {
504 let context = controller.read();
505 (context.direction, context.dragging == Some(panel_index))
506 };
507
508 let cursor = match direction {
509 Direction::Horizontal => CursorIcon::ColResize,
510 _ => CursorIcon::RowResize,
511 };
512
513 let on_capture_global_pointer_move = {
514 let mut controller = controller.clone();
515 move |e: Event<PointerEventData>| {
516 if controller.peek().dragging == Some(panel_index) {
517 e.prevent_default();
518
519 if !*allow_resizing.read() {
520 return;
521 }
522
523 let coords = e.global_location();
524 let handle = size.read();
525 let container = container_size.read();
526 let mut context = controller.write();
527
528 let (drag_delta, container_axis_size) = match context.direction {
529 Direction::Horizontal => {
530 (coords.x as f32 - handle.min_x(), container.width())
531 }
532 Direction::Vertical => {
533 (coords.y as f32 - handle.min_y(), container.height())
534 }
535 };
536
537 let sizes_changed =
538 context.apply_resize(panel_index, drag_delta, container_axis_size);
539
540 if sizes_changed {
541 allow_resizing.set(false);
542 }
543 }
544 }
545 };
546
547 let handle_size = Size::px(ResizableContext::HANDLE_SIZE);
548 let (width, height) = match direction {
549 Direction::Horizontal => (handle_size, Size::fill()),
550 Direction::Vertical => (Size::fill(), handle_size),
551 };
552
553 let background = match *status.read() {
554 HandleStatus::Idle if !dragging => background,
555 _ => hover_background,
556 };
557
558 rect()
559 .width(width)
560 .height(height)
561 .background(background)
562 .corner_radius(corner_radius)
563 .cursor(cursor)
564 .on_sized(move |e: Event<SizedEventData>| {
565 size.set(e.area);
566 allow_resizing.set(true);
567 })
568 .on_pointer_down(move |e: Event<PointerEventData>| {
569 if !e.data().is_primary() {
570 return;
571 }
572 e.stop_propagation();
573 e.prevent_default();
574 controller.write().dragging = Some(panel_index);
575 })
576 .on_pointer_enter(move |_| status.set(HandleStatus::Hovering))
577 .on_capture_global_pointer_move(on_capture_global_pointer_move)
578 .on_pointer_leave(move |_| status.set(HandleStatus::Idle))
579 }
580
581 fn render_key(&self) -> DiffKey {
582 self.key.clone().or(self.default_key())
583 }
584}