diff --git a/AGENTS.md b/AGENTS.md index f26ad96..07392ca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,6 +76,7 @@ GPUI is a UI framework which also provides primitives for state and concurrency - Dependency direction is one-way: `gpui-native-kit` (facade) → `gpui-native-core` / `gpui-native-editor` / … → `gpui-native-theme` + `gpui-native-icons` → gpui. No cycles. Nothing depends on `gpui-component`, `gpui-kit`, `gpuikit`, `bezel`, `native-theme`, or any gpui fork. - `crates/theme` owns the native theme engine and every color, radius, spacing, and metric value — no raw `rgb`/`rgba`/`hsla` or layout `px(...)` literals anywhere else. +- Components always take corner radii from the theme `Radius` token (`cx.theme().radius`); never use gpui's `rounded_*` presets or hardcoded radii in components. - `crates/icons` owns icon resolution; components reference icons by semantic role — no glyph names, unicode symbols, or SVG paths anywhere else. - `crates/core` and its sibling crates own components: each one is a small self-contained implementation gated behind its own cargo feature. Features are additive, gate whole modules only, and may depend on other features. Behavior and presentation live together. No separate behavior layer — promote a shared helper only when a third component needs it. - Components are written in-tree. Other UI toolkits are read for API ideas only; never port their code or depend on them. diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index bdaf4ba..0adcd83 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -56,11 +56,16 @@ impl Global for Theme {} #[derive(Clone, Copy, Debug, PartialEq)] pub struct Radius { pub control: Pixels, + /// Pill and circle shapes. + pub full: Pixels, } impl Default for Radius { fn default() -> Self { - Self { control: px(6.) } + Self { + control: px(6.), + full: px(9999.), + } } } diff --git a/crates/theme/src/resolve.rs b/crates/theme/src/resolve.rs index 8fa6fdc..0343835 100644 --- a/crates/theme/src/resolve.rs +++ b/crates/theme/src/resolve.rs @@ -138,8 +138,12 @@ pub(crate) fn resolve( } let mut radius = Radius::default(); - if let Some(control) = preset.and_then(|def| def.control_radius) { - radius.control = px(control); + if let Some(control) = preset + .and_then(|def| def.control_radius) + .map(px) + .or(system.control_radius) + { + radius.control = control; } ResolvedTheme { @@ -182,11 +186,25 @@ mod tests { variant, accent, palette, + control_radius: None, accessibility: AccessibilityPreferences::default(), source: SourceKind::Macos, } } + #[test] + fn metric_priority_is_definition_then_system_then_fallback() { + let mut stated = system(ThemeVariant::Dark, PartialPalette::default(), None); + stated.control_radius = Some(px(4.)); + + let resolved = resolve(&stated, None, None); + assert_eq!(resolved.radius.control, px(4.)); + + let def = ThemeDefinition::parse_toml("control_radius = 12.0\n").unwrap(); + let overridden = resolve(&stated, Some(&def), None); + assert_eq!(overridden.radius.control, px(12.)); + } + #[test] fn empty_system_resolves_to_the_neutral_fallback() { for variant in [ThemeVariant::Light, ThemeVariant::Dark] { diff --git a/crates/theme/src/source/macos.rs b/crates/theme/src/source/macos.rs index 7a37d9e..e172a2e 100644 --- a/crates/theme/src/source/macos.rs +++ b/crates/theme/src/source/macos.rs @@ -18,6 +18,9 @@ use super::ThemeSource; use crate::ThemeVariant; use crate::system::{AccessibilityPreferences, PartialPalette, SourceKind, SystemTheme}; +/// Default control radius for macOS controls. +const CONTROL_RADIUS_PTS: f32 = 6.0; + #[derive(Default)] pub(crate) struct MacosSource { _observers: Vec>>, @@ -98,6 +101,7 @@ impl ThemeSource for MacosSource { variant, accent: Self::color_to_hsla(&NSColor::controlAccentColor()), palette, + control_radius: Some(gpui::px(CONTROL_RADIUS_PTS)), accessibility, source: SourceKind::Macos, } diff --git a/crates/theme/src/system.rs b/crates/theme/src/system.rs index f27a336..1e1b5b7 100644 --- a/crates/theme/src/system.rs +++ b/crates/theme/src/system.rs @@ -1,6 +1,6 @@ //! The normalized snapshot a platform adapter produces. -use gpui::Hsla; +use gpui::{Hsla, Pixels}; use crate::ThemeVariant; @@ -55,6 +55,7 @@ pub struct SystemTheme { pub variant: ThemeVariant, pub accent: Option, pub palette: PartialPalette, + pub control_radius: Option, pub accessibility: AccessibilityPreferences, pub source: SourceKind, } @@ -65,6 +66,7 @@ impl SystemTheme { variant, accent: None, palette: PartialPalette::default(), + control_radius: None, accessibility: AccessibilityPreferences::default(), source: SourceKind::Fallback, } diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index a9dc855..93fbef2 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -18,6 +18,7 @@ impl Render for Gallery { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = cx.theme(); let colors = theme.colors; + let radius = theme.radius; let variant = theme.variant; let source = gpui_native_theme::detected_source(cx); let mode_override = gpui_native_theme::mode_override(cx); @@ -105,7 +106,8 @@ impl Render for Gallery { .text_sm() .text_color(colors.muted_foreground) .child(format!( - "phase C — button · source: {source} · variant: {variant:?}" + "phase C — button · source: {source} · variant: {variant:?} · control radius {}px", + f32::from(theme.radius.control) )), ), ) @@ -125,6 +127,7 @@ impl Render for Gallery { label, active, colors, + radius, move |_, _, cx| gpui_native_theme::set_mode_override(value, cx), ) })), @@ -146,6 +149,7 @@ impl Render for Gallery { label, active, colors, + radius, move |_, _, cx| { let _ = gpui_native_theme::set_preset(value, cx); }, @@ -208,7 +212,7 @@ impl Render for Gallery { .child( div() .size_6() - .rounded_full() + .rounded(radius.full) .border_1() .border_color(colors.border) .bg(color), @@ -236,7 +240,7 @@ impl Render for Gallery { .child( div() .size_6() - .rounded_full() + .rounded(radius.full) .border_1() .border_color(colors.border) .bg(color), @@ -292,6 +296,7 @@ fn chip( label: SharedString, active: bool, colors: gpui_native_theme::ColorTokens, + radius: gpui_native_theme::Radius, on_click: F, ) -> impl IntoElement + use where @@ -301,7 +306,7 @@ where .id(SharedString::from(id)) .px_3() .py_1() - .rounded_full() + .rounded(radius.full) .border_1() .text_xs() .cursor_pointer()