Theme · Platform: one platform for buttons and caps, icon themes, frosted panes

theme::Platform moves out of typography and shell's ControlsStyle folds
into it; the theme's platform starts as the system's. Window buttons Ely
draws, the title bar's layout and every key cap follow it, secondary
included, through typography::keys::keystroke; a title bar whose buttons
the OS draws follows the OS. primitives::IconTheme lays an owner's rules
by file name and extension over Ely's map, for every file icon.
shell::Vibrancy fills a pane with the palette's new glass, translucent
so a window opened Blurred shows its backdrop, solid under high contrast.
gpui 0.2.2 has no Mica; that line stays blocked.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-27 17:41:15 -04:00
1 parent 7f2205c811
commit d3cfc71ead
30 files changed
+593 -162

No files matched your search

+28 -45
View File
@@ -9,30 +9,9 @@ use smallvec::SmallVec;
use crate::{
primitives::{Icon, IconName},
theme::{ActiveTheme, ControlSize, IconSize, TextSize},
theme::{ActiveTheme, ControlSize, IconSize, Platform, TextSize},
};
/// Which system's window buttons to draw.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ControlsStyle {
Mac,
Windows,
Linux,
}
impl ControlsStyle {
/// The style of the system this build targets.
pub fn current() -> Self {
if cfg!(target_os = "macos") {
Self::Mac
} else if cfg!(target_os = "windows") {
Self::Windows
} else {
Self::Linux
}
}
}
/// Drags the window, and zooms it on double-click.
pub fn drag_region(id: impl Into<ElementId>, window: &mut Window, cx: &mut App) -> Stateful<Div> {
let id = id.into();
@@ -91,7 +70,7 @@ fn run(control: Control, on_close: Option<&OnClose>, window: &mut Window, cx: &m
#[derive(IntoElement)]
pub struct WindowControls {
id: ElementId,
style: ControlsStyle,
platform: Option<Platform>,
on_close: Option<OnClose>,
}
@@ -99,13 +78,14 @@ impl WindowControls {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
style: ControlsStyle::current(),
platform: None,
on_close: None,
}
}
pub fn style(mut self, style: ControlsStyle) -> Self {
self.style = style;
/// Draws this platform's buttons instead of the theme's.
pub fn platform(mut self, platform: Platform) -> Self {
self.platform = Some(platform);
self
}
@@ -118,10 +98,10 @@ impl WindowControls {
impl RenderOnce for WindowControls {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
match self.style {
ControlsStyle::Mac => traffic_lights(self.id, self.on_close, window, cx),
ControlsStyle::Windows => caption_buttons(self.id, self.on_close, window, cx),
ControlsStyle::Linux => round_buttons(self.id, self.on_close, window, cx),
match self.platform.unwrap_or(cx.theme().platform) {
Platform::Mac => traffic_lights(self.id, self.on_close, window, cx),
Platform::Windows => caption_buttons(self.id, self.on_close, window, cx),
Platform::Linux => round_buttons(self.id, self.on_close, window, cx),
}
}
}
@@ -304,7 +284,7 @@ fn route_close(id: &ElementId, handler: Option<OnClose>, window: &mut Window, cx
pub struct TitleBar {
id: ElementId,
title: Option<SharedString>,
style: Option<ControlsStyle>,
platform: Option<Platform>,
on_close: Option<OnClose>,
leading: SmallVec<[AnyElement; 2]>,
actions: SmallVec<[AnyElement; 2]>,
@@ -316,7 +296,7 @@ impl TitleBar {
Self {
id: id.into(),
title: None,
style: None,
platform: None,
on_close: None,
leading: SmallVec::new(),
actions: SmallVec::new(),
@@ -328,13 +308,13 @@ impl TitleBar {
self
}
/// Draws this style's controls instead of the system's.
pub fn style(mut self, style: ControlsStyle) -> Self {
self.style = Some(style);
/// Draws this platform's controls instead of the system's.
pub fn platform(mut self, platform: Platform) -> Self {
self.platform = Some(platform);
self
}
/// Runs instead of closing; without a forced style, system closes run it too.
/// Runs instead of closing; without a forced platform, system closes run it too.
pub fn on_close(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
self.on_close = Some(Rc::new(handler));
self
@@ -355,19 +335,22 @@ impl ParentElement for TitleBar {
impl RenderOnce for TitleBar {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
if self.style.is_none() {
if self.platform.is_none() {
route_close(&self.id, self.on_close.clone(), window, cx);
}
let bar = drag_region(self.id, window, cx);
let theme = cx.theme();
let system = self.style.is_none() && cfg!(target_os = "macos");
let style = self.style.unwrap_or_else(ControlsStyle::current);
let system = self.platform.is_none() && cfg!(target_os = "macos");
let style = match system {
true => Platform::Mac,
false => self.platform.unwrap_or(theme.platform),
};
let mut trailing = (!system).then_some(WindowControls {
id: "window-controls".into(),
style,
platform: Some(style),
on_close: self.on_close,
});
let leading = if style == ControlsStyle::Mac {
let leading = if style == Platform::Mac {
trailing.take()
} else {
None
@@ -379,7 +362,7 @@ impl RenderOnce for TitleBar {
.text_color(theme.colors.fg_muted)
.child(title)
});
let centered = style != ControlsStyle::Windows;
let centered = style != Platform::Windows;
let inset = theme.traffic_light_inset();
let fullscreen = window.is_fullscreen();
let bar = bar
@@ -396,9 +379,9 @@ impl RenderOnce for TitleBar {
let bar = match (system, style) {
(true, _) if fullscreen => bar.pl_3().pr_2(),
(true, _) => bar.pl(inset).pr_2(),
(false, ControlsStyle::Mac) => bar.pl_3().pr_2().children(leading),
(false, ControlsStyle::Windows) => bar.pl_3(),
(false, ControlsStyle::Linux) => bar.pl_3().pr_2(),
(false, Platform::Mac) => bar.pl_3().pr_2().children(leading),
(false, Platform::Windows) => bar.pl_3(),
(false, Platform::Linux) => bar.pl_3().pr_2(),
};
let bar = bar.children(self.leading);
let bar = match (centered, title) {
+3 -1
View File
@@ -11,11 +11,12 @@ mod tabs;
#[cfg(all(test, feature = "test-support"))]
mod tests;
mod update;
mod vibrancy;
mod windows;
pub use activity::{ActivityBar, ActivityItem};
pub use bars::{StatusBar, StatusBarItem, Toolbar, ToolbarGroup, ToolbarSeparator};
pub use chrome::{ControlsStyle, TitleBar, WindowControls, drag_region};
pub use chrome::{TitleBar, WindowControls, drag_region};
pub use crash::CrashReporter;
pub use dialogs::{AboutDialog, SplashScreen};
pub use frame::ResizeBorder;
@@ -25,4 +26,5 @@ pub use status::{Connectivity, OfflineIndicator, ZoomControl};
pub use switcher::WindowSwitcher;
pub use tabs::{TabBar, WindowTab};
pub use update::{UpdateBanner, UpdateDialog, UpdateState};
pub use vibrancy::Vibrancy;
pub use windows::{Corner, Hosted, MiniWindow, WindowManager, open_hosted};
+42
View File
@@ -88,3 +88,45 @@ fn the_switcher_switches_on_release_so_its_opener_stays_shut(cx: &mut TestAppCon
assert!(!desk.read_with(cx, |desk, _| desk.open));
assert_eq!(desk.read_with(cx, |desk, _| desk.enters_above), above);
}
struct Platformed;
impl Render for Platformed {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.flex()
.child(
div()
.debug_selector(|| "controls".into())
.child(super::WindowControls::new("controls")),
)
.child(
div()
.debug_selector(|| "caps".into())
.child(crate::typography::Kbd::new("secondary-s")),
)
}
}
/// Window buttons and key caps follow the theme's platform: Windows draws three wide caption buttons and spells Ctrl.
#[gpui::test]
fn the_themes_platform_draws_the_buttons_and_the_caps(cx: &mut TestAppContext) {
cx.update(Theme::init);
cx.update(|cx| Theme::update(cx, |theme| theme.platform = crate::theme::Platform::Mac));
let (_, cx) = cx.add_window_view(|_, _| Platformed);
settle(cx);
let width = |name: &'static str, cx: &mut VisualTestContext| {
cx.debug_bounds(name).expect("a measured part").size.width
};
let (lights, command) = (width("controls", cx), width("caps", cx));
cx.update(|_, cx| Theme::update(cx, |theme| theme.platform = crate::theme::Platform::Windows));
settle(cx);
assert!(
width("controls", cx) > lights * 2.0,
"caption buttons wider than lights"
);
assert!(
width("caps", cx) > command,
"Ctrl wider than the command mark"
);
}
+42
View File
@@ -0,0 +1,42 @@
use gpui::{
AnyElement, App, Div, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window,
div,
};
use crate::theme::ActiveTheme;
/// A pane the window's blur shows through: vibrancy on macOS, acrylic on Windows. Its window opens with `WindowBackgroundAppearance::Blurred` and nothing opaque beneath the pane; high contrast fills it solid.
#[derive(IntoElement)]
pub struct Vibrancy {
base: Div,
}
impl Vibrancy {
pub fn new() -> Self {
Self { base: div() }
}
}
impl Default for Vibrancy {
fn default() -> Self {
Self::new()
}
}
impl Styled for Vibrancy {
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
}
}
impl ParentElement for Vibrancy {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.base.extend(elements);
}
}
impl RenderOnce for Vibrancy {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
self.base.bg(cx.theme().colors.glass)
}
}