add sizable trait

This commit is contained in:
2026-10-29 07:56:23 +07:00
parent 3808b1519f
commit 1b29c2c940
19 changed files with 442 additions and 69 deletions
+83 -1
View File
@@ -47,6 +47,9 @@ pub struct Theme {
pub colors: ColorTokens,
pub ramps: Ramps,
pub radius: Radius,
/// The platform's native root font size; rem-based sizing scales with it.
pub root_font_size: Pixels,
pub sizes: SizeTable,
pub accessibility: AccessibilityPreferences,
}
@@ -72,6 +75,78 @@ impl Default for Radius {
}
}
/// A named control size class.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Size {
Small,
/// The default.
#[default]
Medium,
Large,
}
/// The control metrics for one size class.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ControlMetrics {
/// Push buttons, toggles, and field rows.
pub control_height: Pixels,
/// Text fields; slightly taller than buttons, as native fields are.
pub input_height: Pixels,
/// Horizontal padding inside controls.
pub padding_x: Pixels,
/// Label text size.
pub text_size: Pixels,
/// Icons and on/off indicators.
pub icon_size: Pixels,
/// Avatars.
pub avatar_size: Pixels,
}
/// The control metrics for every size class, derived from the theme's root
/// font size so controls scale with the platform's native type.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SizeTable {
pub small: ControlMetrics,
pub medium: ControlMetrics,
pub large: ControlMetrics,
}
impl SizeTable {
pub fn with_root_font_size(rem_size: Pixels) -> Self {
let class = |height: f32, padding: f32, text: f32, icon: f32, avatar: f32| ControlMetrics {
control_height: rem_size * height,
input_height: rem_size * (height + 0.125),
padding_x: rem_size * padding,
text_size: rem_size * text,
icon_size: rem_size * icon,
avatar_size: rem_size * avatar,
};
Self {
small: class(1.5, 0.75, 0.85, 0.875, 1.5),
medium: class(1.75, 1.0, 1.0, 1.0, 2.0),
large: class(2.25, 1.25, 1.15, 1.25, 3.0),
}
}
/// The metrics for `size`.
pub fn resolve(&self, size: Size) -> ControlMetrics {
match size {
Size::Small => self.small,
Size::Medium => self.medium,
Size::Large => self.large,
}
}
/// Sets the medium class's text-field height to the platform's stated
/// value, scaling the other classes by the same ratio.
pub fn set_input_height(&mut self, medium_input_height: Pixels) {
let ratio = medium_input_height / self.medium.input_height;
self.small.input_height *= ratio;
self.medium.input_height = medium_input_height;
self.large.input_height *= ratio;
}
}
/// Reads the resolved theme global from any app context.
pub trait ActiveTheme {
fn theme(&self) -> &Theme;
@@ -95,6 +170,8 @@ impl Theme {
colors: resolved.colors,
ramps: resolved.ramps,
radius: resolved.radius,
root_font_size: resolved.root_font_size,
sizes: resolved.sizes,
accessibility: resolved.accessibility,
}
}
@@ -114,7 +191,7 @@ impl Theme {
/// Re-resolves when a window's appearance differs from the resolved variant.
///
/// Wire to `Window::observe_window_appearance`.
pub fn sync_from_window(window: &gpui::Window, cx: &mut App) {
pub fn sync_from_window(window: &mut gpui::Window, cx: &mut App) {
let variant = ThemeVariant::from_appearance(window.appearance());
if cx.global::<Theme>().variant != variant
|| cx.global::<ThemeEngine>().mode_override.is_none()
@@ -122,6 +199,11 @@ impl Theme {
reapply(cx);
}
}
/// Sizes the window's rem to the theme's root font size.
pub fn sync_window_rem_size(window: &mut gpui::Window, cx: &App) {
window.set_rem_size(cx.global::<Theme>().root_font_size);
}
}
/// The engine's knobs and its platform source.