Skip to main content

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}