fc_dioxus/core/measure.rs
1//! The only inline styles this crate writes: custom properties upstream reads from markup.
2
3use dioxus::prelude::*;
4use std::fmt;
5
6/// A percentage, clamped to `0..=100` when constructed so the drawn value and the ARIA
7/// value can never disagree or leave the range.
8///
9/// ```
10/// use fc_dioxus::prelude::Percent;
11/// assert_eq!(Percent::new(140.0).get(), 100.0);
12/// assert_eq!(Percent::new(f64::NAN).get(), 0.0);
13/// ```
14#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
15pub struct Percent(f64);
16impl Percent {
17 /// Clamp a value to `0..=100`; NaN becomes zero rather than poisoning the style.
18 #[must_use]
19 pub fn new(value: f64) -> Self {
20 if value.is_nan() {
21 Self(0.0)
22 } else {
23 Self(value.clamp(0.0, 100.0))
24 }
25 }
26 /// Express `value` out of `max` as a percentage; a non-positive `max` is empty.
27 #[must_use]
28 pub fn of(value: f64, max: f64) -> Self {
29 if max > 0.0 {
30 Self::new(value / max * 100.0)
31 } else {
32 Self(0.0)
33 }
34 }
35 /// The clamped value.
36 #[must_use]
37 pub const fn get(self) -> f64 {
38 self.0
39 }
40}
41impl fmt::Display for Percent {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 // Whole numbers print without a fraction, so markup matches upstream's fixtures.
44 if self.0.fract() == 0.0 {
45 write!(f, "{:.0}", self.0)
46 } else {
47 write!(f, "{}", self.0)
48 }
49 }
50}
51
52/// The custom properties upstream's CSS reads from markup, and nothing else.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub(crate) enum CustomProperty {
55 /// Progress ring fill, 0 to 100.
56 ProgressValue,
57 /// A tool call's determinate progress, as a percentage length.
58 ToolProgress,
59 /// The filled part of a slider's track, as a percentage length.
60 SliderPct,
61 /// A split pane's or resizable element's size.
62 SplitSize,
63 /// A stacked split's block size.
64 SplitBlockSize,
65}
66impl CustomProperty {
67 const fn name(self) -> &'static str {
68 match self {
69 Self::ProgressValue => "--fc-progress-value",
70 Self::ToolProgress => "--fc-tool-progress",
71 Self::SliderPct => "--fc-slider-pct",
72 Self::SplitSize => "--fc-split-size",
73 Self::SplitBlockSize => "--fc-split-block-size",
74 }
75 }
76}
77
78/// A `style` attribute holding exactly one upstream custom property.
79pub(crate) fn custom_property(property: CustomProperty, value: impl fmt::Display) -> Attribute {
80 Attribute::new(
81 "style",
82 format!("{}: {value}", property.name()),
83 None,
84 false,
85 )
86}
87
88/// The data grid's column template: an `auto` track for row headers when present, then
89/// `columns` equal tracks.
90///
91/// The one inline style that is not a custom property. `grid.css` sizes implicit tracks
92/// with `grid-auto-columns`, and rows are `display: contents`, so nothing creates the
93/// tracks and every cell stacks in one column. Upstream's binding and its fixture both
94/// write this declaration inline for that reason; it is layout the markup's shape
95/// decides, not a colour or a theme value.
96pub(crate) fn grid_columns(row_headers: bool, columns: usize) -> Attribute {
97 let lead = if row_headers { "auto " } else { "" };
98 Attribute::new(
99 "style",
100 format!("grid-template-columns: {lead}repeat({columns}, minmax(0, 1fr))"),
101 None,
102 false,
103 )
104}