Skip to main content

kui_core/
metrics.rs

1//! [`Metrics`]: the sizes the stock widgets are built from, one struct
2//! beside the palette.
3//!
4//! Every field is logical px (or a text size in logical px), applied
5//! before `env.scale`, which the renderer multiplies everything by.
6//! Density is the app's choice, as the palette is: [`Metrics::compact`]
7//! is a tighter set, [`Metrics::scaled`] multiplies every length for a
8//! density slider, and `Core::set_metrics` makes one the frame's. An app
9//! that never sets one gets [`Metrics::default`], the stock geometry.
10//!
11//! Fields are roles, like the palette's: `control_pad_x` is a button's
12//! horizontal padding, not "spacing unit 3", and a view that wants a
13//! number between two has arithmetic.
14
15/// The titlebar's height on Windows and everywhere else: the caption
16/// height the OS draws, so the one metric that is the platform's rather
17/// than a density's. Named here so [`Metrics::comfortable`] picks the
18/// running one and the schema row (`MetricRole::platform`) carries both,
19/// which is what keeps a generated table from saying which machine wrote
20/// it.
21pub const TITLEBAR_H_WINDOWS: f32 = 32.0;
22pub const TITLEBAR_H_ELSEWHERE: f32 = 34.0;
23
24/// The sizes the stock widgets are built from. Plain data and [`Copy`]: a
25/// view reads it off `ui.metrics()` and may keep or change its own copy,
26/// and `Core::set_metrics` makes one the frame's.
27#[derive(Clone, Copy, Debug, PartialEq)]
28pub struct Metrics {
29    // -- text ----------------------------------------------------------
30    /// A stock control's label: the button's text size.
31    pub control_text: f32,
32    /// The chrome's text: a menu row, a menu-bar title, the titlebar's
33    /// title.
34    pub chrome_text: f32,
35    /// A tooltip's text.
36    pub hint_text: f32,
37
38    // -- corners -------------------------------------------------------
39    /// The corner of every stock surface: a button, a field, a menu, a
40    /// tooltip.
41    pub radius: f32,
42    /// The corner of a row inside one: a menu row, a menu-bar title.
43    pub radius_inner: f32,
44
45    // -- padding, x then y -----------------------------------------------
46    /// A button's.
47    pub control_pad_x: f32,
48    pub control_pad_y: f32,
49    /// A text field's.
50    pub field_pad_x: f32,
51    pub field_pad_y: f32,
52    /// A tooltip's.
53    pub hint_pad_x: f32,
54    pub hint_pad_y: f32,
55    /// A menu row's; a menu-bar title's is two px shorter, so the bar's
56    /// height and not the title's padding decides the strip.
57    pub menu_pad_x: f32,
58    pub menu_pad_y: f32,
59
60    // -- extents -------------------------------------------------------
61    /// A menu panel's width.
62    pub menu_width: f32,
63    /// The drawn menu bar's height.
64    pub menu_bar_h: f32,
65    /// The titlebar strip's height: the platform's caption height (32 on
66    /// Windows, 34 elsewhere), which [`Metrics::compact`] leaves alone. A
67    /// driver that knows the window's strip is another height makes it
68    /// that window's platform height, and the stock number stands for it
69    /// in [`Core::set_metrics`](crate::runtime::Core::set_metrics)
70    /// (backlog W22): the runner's `Launcher::titlebar` off macOS (40 or
71    /// 52), and under macOS custom chrome the OS's own titlebar, as tall
72    /// as the runner measures it into `env.window.native_controls` (32,
73    /// 40 or 52 on macOS 27). There the strip is drawn at the measured
74    /// height whatever this row says (`widgets::titlebar_height`).
75    pub titlebar_h: f32,
76}
77
78impl Default for Metrics {
79    /// What the widgets have always drawn: the constants `widgets.rs`
80    /// carried, restated once.
81    fn default() -> Self {
82        Self::comfortable()
83    }
84}
85
86impl Metrics {
87    /// The stock set — [`Default`], named.
88    pub const fn comfortable() -> Self {
89        Metrics {
90            control_text: 15.0,
91            chrome_text: 13.0,
92            hint_text: 12.0,
93            radius: 6.0,
94            radius_inner: 4.0,
95            control_pad_x: 14.0,
96            control_pad_y: 8.0,
97            field_pad_x: 10.0,
98            field_pad_y: 8.0,
99            hint_pad_x: 10.0,
100            hint_pad_y: 6.0,
101            menu_pad_x: 8.0,
102            menu_pad_y: 5.0,
103            menu_width: 200.0,
104            menu_bar_h: 26.0,
105            titlebar_h: if cfg!(target_os = "windows") {
106                TITLEBAR_H_WINDOWS
107            } else {
108                TITLEBAR_H_ELSEWHERE
109            },
110        }
111    }
112
113    /// A tighter set for a dense tool — a mux, an inspector, a table of
114    /// controls: smaller text, shallower padding, sharper corners. The
115    /// titlebar keeps the platform's height, since that is the OS's
116    /// number and not a density.
117    pub const fn compact() -> Self {
118        Metrics {
119            control_text: 13.0,
120            chrome_text: 12.0,
121            hint_text: 11.0,
122            radius: 4.0,
123            radius_inner: 3.0,
124            control_pad_x: 10.0,
125            control_pad_y: 5.0,
126            field_pad_x: 8.0,
127            field_pad_y: 5.0,
128            hint_pad_x: 8.0,
129            hint_pad_y: 4.0,
130            menu_pad_x: 8.0,
131            menu_pad_y: 3.0,
132            menu_width: 180.0,
133            menu_bar_h: 22.0,
134            ..Self::comfortable()
135        }
136    }
137
138    /// Every density multiplied by `factor` — a density slider, or an OS
139    /// text-size setting the day one is plumbed. Logical px in, logical
140    /// px out: `env.scale` is applied after this by the renderer and is
141    /// never folded in here. A row the schema marks the platform's
142    /// (`titlebar_h`: the OS's caption height, which the traffic lights
143    /// are drawn against) is left alone, as [`Metrics::compact`] leaves
144    /// it — a 1.5 slider drew a 51 px strip beside 34 px buttons.
145    pub fn scaled(self, factor: f32) -> Self {
146        let mut m = self;
147        for row in crate::schema::METRIC_ROLES {
148            if row.platform.is_some() {
149                continue;
150            }
151            (row.set)(&mut m, (row.get)(&self) * factor);
152        }
153        m
154    }
155}