fc_dioxus/behavior/splitter.rs
1//! Window splitter arithmetic, ported from upstream's `splitter.ts`: clamping and
2//! rounding, what each key does, and how far a drag moves the bar.
3
4use dioxus::prelude::Key;
5
6/// The unit a splitter's value is in.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum SplitUnit {
9 /// A share of the split, so the layout survives a window resize.
10 Percent,
11 /// CSS pixels of the element's border box, for an edge handle.
12 Px,
13}
14impl SplitUnit {
15 /// The CSS unit suffix.
16 #[must_use]
17 pub const fn suffix(self) -> &'static str {
18 match self {
19 Self::Percent => "%",
20 Self::Px => "px",
21 }
22 }
23 /// The default arrow step: 5 for a percentage, 16 for pixels.
24 #[must_use]
25 pub const fn step(self) -> f64 {
26 match self {
27 Self::Percent => 5.0,
28 Self::Px => 16.0,
29 }
30 }
31}
32
33/// Clamp to the bounds and round: tenths of a percent, whole pixels.
34///
35/// ```
36/// use fc_dioxus::behavior::splitter::{settle, SplitUnit};
37/// assert_eq!(settle(35.04, 15.0, 70.0, SplitUnit::Percent), 35.0);
38/// assert_eq!(settle(90.0, 15.0, 70.0, SplitUnit::Percent), 70.0);
39/// assert_eq!(settle(191.6, 120.0, 288.0, SplitUnit::Px), 192.0);
40/// ```
41#[must_use]
42pub fn settle(value: f64, min: f64, max: f64, unit: SplitUnit) -> f64 {
43 let clamped = value.clamp(min, max.max(min));
44 match unit {
45 SplitUnit::Percent => (clamped * 10.0).round() / 10.0,
46 SplitUnit::Px => clamped.round(),
47 }
48}
49
50/// Everything a key press needs to know about the bar.
51#[derive(Debug, Clone, Copy, PartialEq)]
52pub struct KeyContext {
53 /// The bar moves along the inline axis (it is vertical).
54 pub inline: bool,
55 /// The bar sits in right-to-left text.
56 pub rtl: bool,
57 /// +1 when moving the bar toward the end of its axis grows the controlled pane.
58 pub growth: f64,
59 /// The current value.
60 pub current: f64,
61 /// Lower bound.
62 pub min: f64,
63 /// Upper bound.
64 pub max: f64,
65 /// One arrow press; Shift moves ten times as far.
66 pub step: f64,
67 /// The size Enter restores from the minimum, if one is remembered.
68 pub restore: Option<f64>,
69}
70
71/// The value a key asks for, and the size to remember for Enter, or `None` for keys the
72/// splitter leaves alone. Arrows move the bar, not the value: whether that grows the pane
73/// depends on which side of the bar it is on, and in RTL Right is the inline start.
74///
75/// ```
76/// use dioxus::prelude::Key;
77/// use fc_dioxus::behavior::splitter::{key_value, KeyContext};
78/// let bar = KeyContext { inline: true, rtl: false, growth: 1.0, current: 35.0, min: 15.0, max: 70.0, step: 5.0, restore: None };
79/// assert_eq!(key_value(&Key::ArrowRight, false, bar), Some((40.0, None)));
80/// assert_eq!(key_value(&Key::ArrowRight, true, bar), Some((85.0, None)));
81/// assert_eq!(key_value(&Key::ArrowRight, false, KeyContext { rtl: true, ..bar }), Some((30.0, None)));
82/// assert_eq!(key_value(&Key::Enter, false, bar), Some((15.0, Some(35.0))));
83/// assert_eq!(key_value(&Key::Enter, false, KeyContext { current: 15.0, restore: Some(35.0), ..bar }), Some((35.0, None)));
84/// assert_eq!(key_value(&Key::ArrowDown, false, bar), None);
85/// ```
86#[must_use]
87pub fn key_value(key: &Key, shift: bool, bar: KeyContext) -> Option<(f64, Option<f64>)> {
88 let step = bar.step * if shift { 10.0 } else { 1.0 };
89 let toward_end = match (bar.inline, key) {
90 (true, Key::ArrowRight) => {
91 if bar.rtl {
92 -1.0
93 } else {
94 1.0
95 }
96 }
97 (true, Key::ArrowLeft) => {
98 if bar.rtl {
99 1.0
100 } else {
101 -1.0
102 }
103 }
104 (false, Key::ArrowDown) => 1.0,
105 (false, Key::ArrowUp) => -1.0,
106 _ => 0.0,
107 };
108 if toward_end != 0.0 {
109 return Some((bar.current + toward_end * bar.growth * step, None));
110 }
111 match key {
112 Key::Home => Some((bar.min, None)),
113 Key::End => Some((bar.max, None)),
114 Key::Enter if bar.current > bar.min => Some((bar.min, Some(bar.current))),
115 Key::Enter => Some((bar.restore.unwrap_or(bar.max), None)),
116 _ => None,
117 }
118}
119
120/// The value after dragging `delta` pixels from where a drag began at `start`.
121/// `per_pixel` is 100 divided by the split's size for a percentage, 1 for pixels.
122#[must_use]
123pub fn drag_value(start: f64, delta: f64, per_pixel: f64, growth: f64, rtl_inline: bool) -> f64 {
124 let sign = growth * if rtl_inline { -1.0 } else { 1.0 };
125 start + sign * delta * per_pixel
126}