From 7784e8d57041e33fef0db8d49501b9538a3bd882 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Mon, 9 Nov 2026 09:52:29 +0700 Subject: [PATCH] feat: add native material backgrounds --- crates/data/src/sidebar.rs | 31 ++++++++- crates/layout/src/dock.rs | 6 +- crates/layout/src/shell.rs | 32 +++++++-- crates/layout/tests/shell.rs | 3 +- crates/theme/src/colors.rs | 43 ++++++++++++ crates/theme/src/lib.rs | 10 ++- crates/theme/src/resolve.rs | 108 ++++++++++++++++++++++++----- crates/theme/src/source/kde.rs | 1 + crates/theme/src/source/linux.rs | 22 +++++- crates/theme/src/source/macos.rs | 2 + crates/theme/src/source/omarchy.rs | 1 + crates/theme/src/source/windows.rs | 23 +++++- crates/theme/src/system.rs | 5 +- examples/gallery/src/main.rs | 28 +++++++- 14 files changed, 279 insertions(+), 36 deletions(-) diff --git a/crates/data/src/sidebar.rs b/crates/data/src/sidebar.rs index 2b4d897..2b8abfa 100644 --- a/crates/data/src/sidebar.rs +++ b/crates/data/src/sidebar.rs @@ -96,6 +96,7 @@ impl SidebarEntry { pub struct Sidebar { id: ElementId, selected: Option, + transparent: bool, a11y_label: Option, on_change: Option, entries: Option, @@ -110,6 +111,7 @@ impl Sidebar { Self { id: id.into(), selected: None, + transparent: false, a11y_label: None, on_change: None, entries: None, @@ -122,6 +124,17 @@ impl Sidebar { self } + /// Paints translucent material fills so the window's native backdrop shows through. + pub fn transparent(mut self, transparent: bool) -> Self { + self.transparent = transparent; + self + } + + /// Whether translucent material fills are painted. + pub fn is_transparent(&self) -> bool { + self.transparent + } + /// Reports the next selected row, from clicks and keys alike. pub fn on_change(mut self, handler: F) -> Self where @@ -175,6 +188,7 @@ impl RenderOnce for Sidebar { .clone() }); let theme = cx.theme(); + let material = self.transparent; let label = self .a11y_label @@ -195,6 +209,7 @@ impl RenderOnce for Sidebar { index, entries.len(), self.selected == Some(index), + material, &self.on_change, &focus, ) @@ -207,6 +222,7 @@ impl RenderOnce for Sidebar { .size_full() .flex() .flex_col() + .when(material, |sidebar| sidebar.bg(theme.material.background)) .text_size(theme.sizes.medium.text_size) .track_focus(&focus) .focusable() @@ -237,16 +253,19 @@ impl RenderOnce for Sidebar { } } +#[allow(clippy::too_many_arguments)] fn render_entry( cx: &App, entry: &SidebarEntry, index: usize, count: usize, selected: bool, + material: bool, on_change: &Option, focus: &FocusHandle, ) -> AnyElement { let theme = cx.theme(); + match entry { SidebarEntry::Header { label } => div() .px_2() @@ -277,7 +296,17 @@ fn render_entry( .aria_selected(selected) .aria_position_in_set(index + 1) .aria_size_of_set(count) - .map(|row| paint_selectable_row(cx, row, selected, *row_disabled)); + .map(|row| { + if material && !*row_disabled { + if selected { + row.bg(theme.material.selection) + } else { + row.hover(|row| row.bg(theme.material.hover)) + } + } else { + paint_selectable_row(cx, row, selected, *row_disabled) + } + }); if !*row_disabled { row = row.cursor_pointer().on_click({ diff --git a/crates/layout/src/dock.rs b/crates/layout/src/dock.rs index 317bb05..6bf38bf 100644 --- a/crates/layout/src/dock.rs +++ b/crates/layout/src/dock.rs @@ -1078,8 +1078,10 @@ fn render_pane( .w_full() .h(titlebar.height) .items_center() - .when(titlebar.traffic_light_pad, |bar| { - bar.pl(px(TRAFFIC_LIGHT_PAD)) + .pl(if titlebar.traffic_light_pad { + px(TRAFFIC_LIGHT_PAD) + } else { + padding_x }) .pr(padding_x) .child(tabs) diff --git a/crates/layout/src/shell.rs b/crates/layout/src/shell.rs index 4b7f1bd..6d0471b 100644 --- a/crates/layout/src/shell.rs +++ b/crates/layout/src/shell.rs @@ -178,9 +178,11 @@ impl RenderOnce for Shell { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let theme = cx.theme(); let metrics = theme.sizes.resolve(Size::default()); + let background = theme.colors.background; + let titlebar_height = theme.titlebar_height; - let state = self.state; let id = self.id.clone(); + let state = self.state; let mut left = self.left_sidebar; let mut right = self.right_sidebar; @@ -188,6 +190,9 @@ impl RenderOnce for Shell { let left_visible = state.read(cx).is_left_sidebar_visible(); let right_visible = state.read(cx).is_right_sidebar_visible(); + let left_material = left + .as_ref() + .is_some_and(|sidebar| sidebar.is_transparent()); let layout = div() .flex() @@ -204,8 +209,9 @@ impl RenderOnce for Shell { .map(|sidebar| sidebar.into_any_element()) .unwrap_or_else(|| div().into_any_element()), Side::Left, + left_material, &state, - metrics.strip_height, + titlebar_height, metrics.dense_padding_x, window, cx, @@ -223,6 +229,7 @@ impl RenderOnce for Shell { .h_full() .min_w_0() .overflow_hidden() + .bg(background) .child(center.take().unwrap_or_else(|| div().into_any_element())), ) .when(right_visible && right.is_some(), |layout| { @@ -238,8 +245,9 @@ impl RenderOnce for Shell { width, right.take().unwrap_or_else(|| div().into_any_element()), Side::Right, + false, &state, - metrics.strip_height, + titlebar_height, metrics.dense_padding_x, window, cx, @@ -257,17 +265,26 @@ fn sidebar_region( width: Pixels, content: AnyElement, side: Side, + material: bool, state: &Entity, strip_height: Pixels, padding_x: Pixels, window: &mut Window, cx: &mut App, ) -> AnyElement { + let theme = cx.theme(); + let background = theme.colors.background; + let strip_background = match (side, material) { + (Side::Left, true) => theme.material.background, + _ => theme.colors.background, + }; + let strip = h_flex() .id(SharedString::from(format!("sidebar-strip-{}", side.name()))) .debug_selector(move || format!("sidebar-strip-{}", side.name())) .flex_none() .h(strip_height) + .bg(strip_background) .items_center() .justify_end() .map(|strip| titlebar_strip(strip, window, cx)) @@ -305,6 +322,13 @@ fn sidebar_region( .h_full() .overflow_hidden() .child(strip) - .child(div().flex_1().min_h_0().overflow_hidden().child(content)) + .child( + div() + .flex_1() + .min_h_0() + .overflow_hidden() + .when(!material, |content| content.bg(background)) + .child(content), + ) .into_any_element() } diff --git a/crates/layout/tests/shell.rs b/crates/layout/tests/shell.rs index c75a400..95583a1 100644 --- a/crates/layout/tests/shell.rs +++ b/crates/layout/tests/shell.rs @@ -146,12 +146,13 @@ fn the_header_toggle_hides_its_sidebar() { // icon-only small button: derive its center from the theme metrics. let metrics = cx.update(|cx| cx.theme().sizes.resolve(Size::default())); let small = cx.update(|cx| cx.theme().sizes.resolve(Size::Small)); + let strip_height = cx.update(|cx| cx.theme().titlebar_height); let region = bounds(&mut visual, "sidebar-test-left").expect("left sidebar missing"); // The Sidebar fills the content area below the strip; the toggle sits at // the strip's trailing end, one strip height above the sidebar's top. let toggle = point( region.right() - metrics.dense_padding_x - small.control_height / 2.0, - region.top() - metrics.strip_height / 2.0, + region.top() - strip_height / 2.0, ); press(&mut visual, toggle, 1); redraw(&mut visual); diff --git a/crates/theme/src/colors.rs b/crates/theme/src/colors.rs index ed2c51f..6c047c9 100644 --- a/crates/theme/src/colors.rs +++ b/crates/theme/src/colors.rs @@ -3,6 +3,8 @@ use gpui::{Hsla, hsla, rgb}; +use crate::ThemeVariant; + fn hex(hex: u32) -> Hsla { rgb(hex).into() } @@ -115,3 +117,44 @@ impl ColorTokens { } } } + +/// The translucent paints surfaces lay over a native window material. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct MaterialTokens { + /// The window-sized fill the material shows through. + pub background: Hsla, + /// The hover wash over material surfaces. + pub hover: Hsla, + /// The selected-row fill over material surfaces. + pub selection: Hsla, +} + +impl MaterialTokens { + /// Derives the material paints from the resolved palette. + pub(crate) fn derive( + colors: &ColorTokens, + selection: Hsla, + variant: ThemeVariant, + translucent: bool, + ) -> Self { + if !translucent { + return Self { + background: colors.background, + hover: colors.muted, + selection, + }; + } + + // Light tints heavier than dark: the blur carries less contrast there. + let background_alpha = match variant { + ThemeVariant::Light => 0.6, + ThemeVariant::Dark => 0.5, + }; + Self { + background: colors.background.alpha(background_alpha), + // A foreground wash keeps hover visible over any backdrop hue. + hover: colors.foreground.alpha(0.06), + selection: selection.alpha(0.85), + } + } +} diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index ce7b09a..4bac397 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -14,13 +14,13 @@ mod resolve; mod source; mod system; -pub use colors::ColorTokens; +pub use colors::{ColorTokens, MaterialTokens}; pub use definition::ThemeDefinition; pub use ramps::{AccentShades, Oklch}; pub use resolve::{Ramps, ResolvedTheme}; pub use system::{AccessibilityPreferences, PartialPalette, SourceKind, SystemTheme}; -use gpui::{App, Global, Hsla, Pixels, WindowAppearance, px}; +use gpui::{App, Global, Hsla, Pixels, WindowAppearance, WindowBackgroundAppearance, px}; use std::time::Duration; /// The hairline width of the control border every button and field paints; @@ -50,6 +50,8 @@ impl ThemeVariant { pub struct Theme { pub variant: ThemeVariant, pub colors: ColorTokens, + /// The translucent paints over the window's native material. + pub material: MaterialTokens, pub ramps: Ramps, pub radius: Radius, /// Per-platform border and focus chrome. @@ -68,6 +70,8 @@ pub struct Theme { pub mono_font: &'static str, pub sizes: SizeTable, pub accessibility: AccessibilityPreferences, + /// The material a window should open with, after accessibility. + pub window_background: WindowBackgroundAppearance, } impl Global for Theme {} @@ -524,6 +528,7 @@ impl Theme { Self { variant: resolved.variant, colors: resolved.colors, + material: resolved.material, ramps: resolved.ramps, radius: resolved.radius, borders: resolved.borders, @@ -536,6 +541,7 @@ impl Theme { mono_font: resolved.mono_font, sizes: resolved.sizes, accessibility: resolved.accessibility, + window_background: resolved.window_background, } } diff --git a/crates/theme/src/resolve.rs b/crates/theme/src/resolve.rs index fcaf96f..1556d43 100644 --- a/crates/theme/src/resolve.rs +++ b/crates/theme/src/resolve.rs @@ -2,9 +2,10 @@ use crate::definition::ThemeDefinition; use crate::ramps::{AccentShades, Oklch, contrast_foreground, derive_shades, mix}; use crate::system::SystemTheme; use crate::{ - Borders, ColorTokens, Elevations, Motion, Radius, Scrollbar, SizeTable, Spacing, ThemeVariant, + Borders, ColorTokens, Elevations, MaterialTokens, Motion, Radius, Scrollbar, SizeTable, + Spacing, ThemeVariant, }; -use gpui::px; +use gpui::{WindowBackgroundAppearance, px}; /// The floor of a custom titlebar's height, in px at 100% scaling: the height /// macOS and Linux titlebars draw at regardless of the control scale. @@ -26,6 +27,8 @@ pub struct Ramps { pub struct ResolvedTheme { pub variant: ThemeVariant, pub colors: ColorTokens, + /// The translucent paints over the window's native material. + pub material: MaterialTokens, pub ramps: Ramps, pub radius: Radius, pub borders: Borders, @@ -41,6 +44,8 @@ pub struct ResolvedTheme { pub mono_font: &'static str, pub sizes: SizeTable, pub accessibility: crate::system::AccessibilityPreferences, + /// The material a window should open with, after accessibility. + pub window_background: WindowBackgroundAppearance, } /// Overlay order (later wins): neutral base → system → preset definition. @@ -187,6 +192,15 @@ pub(crate) fn resolve( ), }; + // Reduced transparency forces the window opaque. + let window_background = if system.accessibility.reduce_transparency { + WindowBackgroundAppearance::Opaque + } else { + system + .window_background + .unwrap_or(WindowBackgroundAppearance::Opaque) + }; + let accessibility = system.accessibility; if accessibility.high_contrast { colors.border = mix( @@ -255,25 +269,35 @@ pub(crate) fn resolve( radius.sm = sm; } + let ramps = Ramps { + primary: derive_shades(primary, colors.background, variant == ThemeVariant::Dark), + secondary: derive_shades( + colors.secondary, + colors.background, + variant == ThemeVariant::Dark, + ), + destructive: derive_shades( + destructive, + colors.background, + variant == ThemeVariant::Dark, + ), + success: derive_shades(success, colors.background, variant == ThemeVariant::Dark), + warning: derive_shades(warning, colors.background, variant == ThemeVariant::Dark), + info: derive_shades(info, colors.background, variant == ThemeVariant::Dark), + }; + + let material = MaterialTokens::derive( + &colors, + ramps.primary.base, + variant, + window_background != WindowBackgroundAppearance::Opaque, + ); + ResolvedTheme { variant, - ramps: Ramps { - primary: derive_shades(primary, colors.background, variant == ThemeVariant::Dark), - secondary: derive_shades( - colors.secondary, - colors.background, - variant == ThemeVariant::Dark, - ), - destructive: derive_shades( - destructive, - colors.background, - variant == ThemeVariant::Dark, - ), - success: derive_shades(success, colors.background, variant == ThemeVariant::Dark), - warning: derive_shades(warning, colors.background, variant == ThemeVariant::Dark), - info: derive_shades(info, colors.background, variant == ThemeVariant::Dark), - }, + ramps, colors, + material, radius, borders: Borders::for_platform(system.source, variant), spacing: Spacing { @@ -290,6 +314,7 @@ pub(crate) fn resolve( mono_font: mono_font(), sizes, accessibility, + window_background, } } @@ -308,7 +333,7 @@ fn mono_font() -> &'static str { mod tests { use super::*; use crate::system::{AccessibilityPreferences, PartialPalette, SourceKind}; - use gpui::{Rgba, rgb}; + use gpui::{Rgba, WindowBackgroundAppearance, rgb}; fn hsla(hex: u32) -> gpui::Hsla { gpui::Hsla::from(rgb(hex)) @@ -335,6 +360,7 @@ mod tests { titlebar_height: None, root_font_size: None, accessibility: AccessibilityPreferences::default(), + window_background: None, source: SourceKind::Macos, } } @@ -537,6 +563,50 @@ mod tests { } } + #[test] + fn the_platforms_window_material_resolves_into_the_theme() { + let mut stated = system(ThemeVariant::Light, PartialPalette::default(), None); + stated.window_background = Some(WindowBackgroundAppearance::MicaBackdrop); + let resolved = resolve(&stated, None, None); + + assert_eq!( + resolved.window_background, + WindowBackgroundAppearance::MicaBackdrop + ); + // Material surfaces stay translucent over the stated backdrop. + assert!(resolved.material.background.a < resolved.colors.background.a); + assert!(resolved.material.selection.a < 1.0); + + let unstated = resolve( + &system(ThemeVariant::Light, PartialPalette::default(), None), + None, + None, + ); + assert_eq!( + unstated.window_background, + WindowBackgroundAppearance::Opaque + ); + assert_eq!(unstated.material.background, unstated.colors.background); + assert_eq!(unstated.material.hover, unstated.colors.muted); + assert_eq!(unstated.material.selection, unstated.ramps.primary.base); + } + + #[test] + fn reduced_transparency_forces_an_opaque_window_and_material() { + let mut stated = system(ThemeVariant::Dark, PartialPalette::default(), None); + stated.window_background = Some(WindowBackgroundAppearance::Blurred); + stated.accessibility.reduce_transparency = true; + let resolved = resolve(&stated, None, None); + + assert_eq!( + resolved.window_background, + WindowBackgroundAppearance::Opaque + ); + assert_eq!(resolved.material.background, resolved.colors.background); + assert_eq!(resolved.material.hover, resolved.colors.muted); + assert_eq!(resolved.material.selection, resolved.ramps.primary.base); + } + #[test] fn overlay_is_translucent_and_overridable() { let light = resolve( diff --git a/crates/theme/src/source/kde.rs b/crates/theme/src/source/kde.rs index e9a6acd..da46ec8 100644 --- a/crates/theme/src/source/kde.rs +++ b/crates/theme/src/source/kde.rs @@ -93,6 +93,7 @@ impl KdeScheme { titlebar_height: Some(px(44.)), root_font_size: Some(self.root_font_size.unwrap_or(px(13.33))), accessibility, + window_background: None, source: SourceKind::Kde, } } diff --git a/crates/theme/src/source/linux.rs b/crates/theme/src/source/linux.rs index c6acece..717d25d 100644 --- a/crates/theme/src/source/linux.rs +++ b/crates/theme/src/source/linux.rs @@ -38,6 +38,12 @@ fn home_dir() -> Option { std::env::var_os("HOME").map(PathBuf::from) } +/// Whether the session runs on Wayland, where compositors state blur protocols. +fn wayland_session() -> bool { + std::env::var_os("WAYLAND_DISPLAY").is_some() + || std::env::var("XDG_SESSION_TYPE").is_ok_and(|session| session == "wayland") +} + fn detect(home: &Path, portal_reachable: bool, gsettings_reachable: bool) -> Backend { let omarchy_marker = home .join(".local/state/omarchy/current/theme.name") @@ -164,6 +170,7 @@ impl LinuxSource { .unwrap_or(gpui::px(14.67)), ), accessibility, + window_background: None, source: SourceKind::Gnome, } } @@ -203,15 +210,24 @@ impl ThemeSource for LinuxSource { match self.backend { Backend::Omarchy => omarchy::io::read_current() - .map(|theme| theme.into_system(accessibility)) + .map(|theme| { + let mut system = theme.into_system(accessibility); + // Hyprland blurs transparent surfaces on its own. + system.window_background = Some(gpui::WindowBackgroundAppearance::Blurred); + system + }) .unwrap_or_else(|| SystemTheme::fallback(variant)), Backend::Kde => kde::io::read_current() .map(|scheme| { - scheme.into_system( + let mut system = scheme.into_system( accessibility, portal_appearance.and_then(|appearance| appearance.accent), portal_appearance.map(|appearance| appearance.variant(variant)), - ) + ); + // KWin states the blur protocols; X11 compositors do not. + system.window_background = + wayland_session().then_some(gpui::WindowBackgroundAppearance::Blurred); + system }) .unwrap_or_else(|| SystemTheme::fallback(variant)), Backend::Portal => self.portal_snapshot(variant, portal_appearance, accessibility), diff --git a/crates/theme/src/source/macos.rs b/crates/theme/src/source/macos.rs index 30e3763..00e1dc3 100644 --- a/crates/theme/src/source/macos.rs +++ b/crates/theme/src/source/macos.rs @@ -121,6 +121,8 @@ impl ThemeSource for MacosSource { variant, accent: Self::color_to_hsla(&NSColor::controlAccentColor()), palette, + // macOS blurs what is behind the window natively. + window_background: Some(gpui::WindowBackgroundAppearance::Blurred), control_radius: Some(gpui::px(CONTROL_RADIUS_PTS)), overlay_radius: None, sm_radius: None, diff --git a/crates/theme/src/source/omarchy.rs b/crates/theme/src/source/omarchy.rs index 890c63b..371878d 100644 --- a/crates/theme/src/source/omarchy.rs +++ b/crates/theme/src/source/omarchy.rs @@ -72,6 +72,7 @@ impl OmarchyTheme { titlebar_height: None, root_font_size: None, accessibility, + window_background: None, source: SourceKind::Omarchy, } } diff --git a/crates/theme/src/source/windows.rs b/crates/theme/src/source/windows.rs index a2f66aa..6542728 100644 --- a/crates/theme/src/source/windows.rs +++ b/crates/theme/src/source/windows.rs @@ -31,12 +31,12 @@ const TITLEBAR_HEIGHT_PX: f32 = 32.0; /// WinUI 11's default body text size, in pixels at 100% scaling. const ROOT_FONT_SIZE_PX: f32 = 14.0; -/// The `Personalize` registry values, read once at startup as the fallback -/// when the WinRT surface is unavailable. +/// The `Personalize` registry values and the OS build number. #[derive(Clone, Copy, Debug, Default)] struct RegistrySettings { apps_use_light_theme: Option, enable_transparency: Option, + current_build: Option, } impl RegistrySettings { @@ -45,6 +45,9 @@ impl RegistrySettings { windows_registry::CURRENT_USER .open(r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize") }; + let current_version = || { + windows_registry::LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion") + }; Self { apps_use_light_theme: personalize() .and_then(|key| key.get_u32("AppsUseLightTheme")) @@ -54,6 +57,10 @@ impl RegistrySettings { .and_then(|key| key.get_u32("EnableTransparency")) .ok() .map(|value| value != 0), + current_build: current_version() + .and_then(|key| key.get_string("CurrentBuildNumber")) + .ok() + .and_then(|build| build.parse::().ok()), } } } @@ -130,6 +137,17 @@ impl WindowsSource { .unwrap_or(false), } } + + /// The native backdrop: Mica on Windows 11, acrylic on Windows 10 1809+. + fn window_material(&self) -> Option { + match self.registry.current_build { + // 22621: Windows 11 22H2, where the DWM backdrop types exist. + Some(build) if build >= 22621 => Some(gpui::WindowBackgroundAppearance::MicaBackdrop), + // 17763: Windows 10 1809, where acrylic blur-behind exists. + Some(build) if build >= 17763 => Some(gpui::WindowBackgroundAppearance::Blurred), + _ => None, + } + } } impl ThemeSource for WindowsSource { @@ -180,6 +198,7 @@ impl ThemeSource for WindowsSource { titlebar_height: Some(gpui::px(TITLEBAR_HEIGHT_PX)), root_font_size: Some(gpui::px(ROOT_FONT_SIZE_PX * text_scale as f32)), accessibility: self.accessibility(), + window_background: self.window_material(), source: SourceKind::Windows, } } diff --git a/crates/theme/src/system.rs b/crates/theme/src/system.rs index 7be281b..0cf4a4e 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, Pixels, px}; +use gpui::{Hsla, Pixels, WindowBackgroundAppearance, px}; use crate::ThemeVariant; @@ -115,6 +115,8 @@ pub struct SystemTheme { /// The platform's native root font size, if it has one. pub root_font_size: Option, pub accessibility: AccessibilityPreferences, + /// The window material the platform natively applies, if any. + pub window_background: Option, pub source: SourceKind, } @@ -136,6 +138,7 @@ impl SystemTheme { titlebar_height: None, root_font_size: None, accessibility: AccessibilityPreferences::default(), + window_background: None, source: SourceKind::Fallback, } } diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index fd5fa76..6f1a053 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -223,6 +223,8 @@ struct Gallery { palette_runs: Vec, dialog_open: bool, alert_open: bool, + /// Whether the app sidebar paints the window's native material. + sidebar_transparent: bool, } impl Gallery { @@ -254,6 +256,7 @@ impl Gallery { let left_sidebar = Sidebar::new("gallery-sidebar") .selected(selected) + .transparent(self.sidebar_transparent) .accessibility_label("Component catalog") .entries(|_, _| { CATALOG @@ -404,10 +407,12 @@ impl Gallery { PanelDescriptor::new("welcome", "Welcome", { let layout_state = layout_state.clone(); let dock_state = dock_state.clone(); + let gallery = gallery.clone(); move |_, cx| { cx.new(|_| WelcomePanel { layout_state: layout_state.clone(), dock_state: dock_state.clone(), + gallery: gallery.clone(), }) .into() } @@ -729,7 +734,6 @@ impl Render for Gallery { palette.update(cx, |palette, cx| palette.toggle(window, cx)); } }) - .bg(theme.colors.background) .text_color(theme.colors.foreground) .child(self.render_shell(cx)) .child(dialog) @@ -745,6 +749,7 @@ impl Render for Gallery { struct WelcomePanel { layout_state: Entity, dock_state: Entity, + gallery: WeakEntity, } impl Render for WelcomePanel { @@ -753,6 +758,7 @@ impl Render for WelcomePanel { let layout_left = self.layout_state.clone(); let layout_right = self.layout_state.clone(); let dock_state = self.dock_state.clone(); + let gallery = self.gallery.clone(); ScrollArea::new("welcome-demo") .size_full() @@ -811,6 +817,21 @@ impl Render for WelcomePanel { }); }), ) + .child( + Button::new("welcome-toggle-blur") + .label("Toggle sidebar blur") + .variant(ButtonVariant::Secondary) + .small() + .on_click(move |_, _, cx| { + gallery + .update(cx, |gallery, cx| { + gallery.sidebar_transparent = + !gallery.sidebar_transparent; + cx.notify(); + }) + .ok(); + }), + ) .child( Button::new("welcome-split") .label("Split active right") @@ -3774,6 +3795,7 @@ impl Render for SidebarDemo { .child( Sidebar::new("sidebar-demo-list") .selected(self.selected) + .transparent(true) .on_change(move |selected, _, cx| { view.update(cx, |demo, cx| { demo.selected = selected; @@ -4077,6 +4099,9 @@ fn main() { // The strips move the window themselves, so a tab dragged inside // the titlebar band never drags the window with it. app_owns_titlebar_drag: true, + // The platform's native backdrop; the theme's material paints + // resolve over it and go opaque under reduced transparency. + window_background: cx.theme().window_background, ..Default::default() }, |window, cx| { @@ -4106,6 +4131,7 @@ fn main() { palette_runs: Vec::new(), dialog_open: false, alert_open: false, + sidebar_transparent: true, } }); let palette = cx.new(|cx| {