workspace: Rework background image to VS Code-style surface tint
Replaces the whole-UI opacity hack with a theme-level surface tint: - background.opacity now scales the alpha of editor/terminal/panel surface colors only, so the image shows through the code area while text, borders, tabs and chrome stay fully opaque and readable - the image element renders at full opacity behind the workspace - theme reloads automatically when background settings change
This commit is contained in:
1 parent
ada1148914
commit
ef0ddca6ce
7 files changed
+66
-58
No files matched your search
@@ -604,11 +604,10 @@
|
||||
"path": null,
|
||||
// How the image is fitted into the workspace bounds: "cover", "fill" or "contain".
|
||||
"fit": "cover",
|
||||
// The opacity of the background image, between 0.0 (fully transparent) and 1.0 (fully opaque).
|
||||
"opacity": 0.6,
|
||||
// The opacity of the UI drawn on top of the image, between 0.0 (UI fully
|
||||
// transparent, image fully visible) and 1.0 (opaque UI).
|
||||
"ui_opacity": 1.0,
|
||||
// The opacity of the editor and terminal surfaces drawn on top of the image,
|
||||
// between 0.0 (surfaces fully transparent, image fully visible) and 1.0
|
||||
// (surfaces fully opaque, image hidden).
|
||||
"opacity": 0.15,
|
||||
},
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
|
||||
@@ -34,17 +34,11 @@ pub struct BackgroundImageSettingsContent {
|
||||
/// Default: cover
|
||||
pub fit: Option<BackgroundImageFit>,
|
||||
|
||||
/// The opacity of the background image, between 0.0 (fully transparent)
|
||||
/// and 1.0 (fully opaque).
|
||||
/// The opacity of the editor and terminal surfaces drawn on top of the
|
||||
/// image, between 0.0 (surfaces fully transparent, image fully visible)
|
||||
/// and 1.0 (surfaces fully opaque, image hidden). Lower values let the
|
||||
/// image show through the code area; text and chrome stay unaffected.
|
||||
///
|
||||
/// Default: 0.6
|
||||
/// Default: 0.15
|
||||
pub opacity: Option<f32>,
|
||||
|
||||
/// The opacity applied to the UI elements drawn on top of the image,
|
||||
/// between 0.0 (fully transparent UI, image fully visible) and 1.0
|
||||
/// (opaque UI, the default). Lower this to make the image show through
|
||||
/// panels and editors.
|
||||
///
|
||||
/// Default: 1.0
|
||||
pub ui_opacity: Option<f32>,
|
||||
}
|
||||
@@ -1530,7 +1530,7 @@ fn appearance_page() -> SettingsPage {
|
||||
]
|
||||
}
|
||||
|
||||
fn background_image_section() -> [SettingsPageItem; 5] {
|
||||
fn background_image_section() -> [SettingsPageItem; 4] {
|
||||
[
|
||||
SettingsPageItem::SectionHeader("Background Image"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
@@ -1573,7 +1573,7 @@ fn appearance_page() -> SettingsPage {
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Opacity",
|
||||
description: "Opacity of the background image, between 0.0 (fully transparent) and 1.0 (fully opaque).",
|
||||
description: "Opacity of the editor and terminal surfaces, between 0.0 (image fully visible) and 1.0 (image hidden).",
|
||||
field: Box::new(SettingField {
|
||||
organization_override: None,
|
||||
json_path: Some("background.opacity"),
|
||||
@@ -1590,25 +1590,6 @@ fn appearance_page() -> SettingsPage {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "UI Opacity",
|
||||
description: "Opacity of the UI drawn on top of the image, between 0.0 (image fully visible) and 1.0 (opaque UI).",
|
||||
field: Box::new(SettingField {
|
||||
organization_override: None,
|
||||
json_path: Some("background.ui_opacity"),
|
||||
pick: |settings_content| {
|
||||
settings_content.background.as_ref()?.ui_opacity.as_ref()
|
||||
},
|
||||
write: |settings_content, value, _| {
|
||||
settings_content
|
||||
.background
|
||||
.get_or_insert_default()
|
||||
.ui_opacity = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,6 @@ impl ThemeSelection {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [ThemeMode] for the [ThemeSelection].
|
||||
pub fn mode(&self) -> Option<ThemeAppearanceMode> {
|
||||
match self {
|
||||
|
||||
@@ -100,6 +100,7 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
|
||||
settings.experimental_theme_overrides.clone(),
|
||||
settings.theme_overrides.clone(),
|
||||
);
|
||||
let mut prev_background = background_tint_input(cx);
|
||||
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
@@ -116,6 +117,7 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
|
||||
settings.experimental_theme_overrides.clone(),
|
||||
settings.theme_overrides.clone(),
|
||||
);
|
||||
let background = background_tint_input(cx);
|
||||
|
||||
if buffer_font_size_settings != prev_buffer_font_size_settings {
|
||||
prev_buffer_font_size_settings = buffer_font_size_settings;
|
||||
@@ -147,9 +149,13 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
|
||||
reset_markdown_preview_font_size(cx);
|
||||
}
|
||||
|
||||
if theme_name != prev_theme_name || theme_overrides != prev_theme_overrides {
|
||||
if theme_name != prev_theme_name
|
||||
|| theme_overrides != prev_theme_overrides
|
||||
|| background != prev_background
|
||||
{
|
||||
prev_theme_name = theme_name;
|
||||
prev_theme_overrides = theme_overrides;
|
||||
prev_background = background;
|
||||
reload_theme(cx);
|
||||
}
|
||||
|
||||
@@ -161,6 +167,15 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
|
||||
.detach();
|
||||
}
|
||||
|
||||
|
||||
/// The parts of the background image settings that affect the theme tint.
|
||||
fn background_tint_input(cx: &App) -> Option<(Option<String>, Option<f32>)> {
|
||||
let background = cx
|
||||
.try_global::<SettingsStore>()
|
||||
.and_then(|store| store.merged_settings().background.clone())?;
|
||||
Some((background.path, background.opacity))
|
||||
}
|
||||
|
||||
fn configured_theme(cx: &mut App) -> Arc<Theme> {
|
||||
let themes = ThemeRegistry::default_global(cx);
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
@@ -179,7 +194,38 @@ fn configured_theme(cx: &mut App) -> Arc<Theme> {
|
||||
.unwrap_or_else(|_| themes.get(DEFAULT_DARK_THEME).unwrap())
|
||||
}
|
||||
};
|
||||
theme_settings.apply_theme_overrides(theme)
|
||||
let theme = theme_settings.apply_theme_overrides(theme);
|
||||
tint_for_background_image(theme, cx)
|
||||
}
|
||||
|
||||
/// Tints the editor and terminal surface colors to let the background image
|
||||
/// show through. Only applies while a background image path is configured.
|
||||
/// Text and chrome colors are left untouched so readability is preserved.
|
||||
fn tint_for_background_image(theme: Arc<Theme>, cx: &App) -> Arc<Theme> {
|
||||
let Some(background) = cx
|
||||
.try_global::<SettingsStore>()
|
||||
.and_then(|store| store.merged_settings().background.clone())
|
||||
else {
|
||||
return theme;
|
||||
};
|
||||
if background.path.is_none() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
let opacity = background.opacity.unwrap_or(0.15).clamp(0.0, 1.0);
|
||||
if opacity >= 1.0 {
|
||||
return theme;
|
||||
}
|
||||
|
||||
let mut theme = (*theme).clone();
|
||||
let colors = &mut theme.styles.colors;
|
||||
colors.background.a *= opacity;
|
||||
colors.editor_background.a *= opacity;
|
||||
colors.editor_gutter_background.a *= opacity;
|
||||
colors.editor_subheader_background.a *= opacity;
|
||||
colors.terminal_background.a *= opacity;
|
||||
colors.panel_background.a *= opacity;
|
||||
Arc::new(theme)
|
||||
}
|
||||
|
||||
fn configured_icon_theme(cx: &mut App) -> Arc<theme::IconTheme> {
|
||||
|
||||
@@ -14,8 +14,9 @@ use image::Frame;
|
||||
use settings::{BackgroundImageFit, RegisterSetting, Settings, SettingsStore};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// Default opacity of the workspace background image.
|
||||
pub const DEFAULT_BACKGROUND_IMAGE_OPACITY: f32 = 0.6;
|
||||
/// Default opacity of the editor and terminal surfaces drawn on top of
|
||||
/// the background image. Lower values let the image show through more.
|
||||
pub const DEFAULT_BACKGROUND_IMAGE_OPACITY: f32 = 0.15;
|
||||
/// Maximum time to wait for the image file to become readable before falling
|
||||
/// back to the local cache. Bounds how long an unreachable path (e.g. a dead
|
||||
/// network mount) can delay the background image; the workspace itself never
|
||||
@@ -29,10 +30,10 @@ pub struct BackgroundImageSettings {
|
||||
pub path: Option<Arc<Path>>,
|
||||
/// How the image is fitted into the workspace bounds.
|
||||
pub fit: BackgroundImageFit,
|
||||
/// The opacity of the image, between 0.0 and 1.0.
|
||||
/// The opacity of the editor and terminal surfaces drawn on top of the
|
||||
/// image, between 0.0 (surfaces fully transparent) and 1.0 (surfaces
|
||||
/// fully opaque, image hidden).
|
||||
pub opacity: f32,
|
||||
/// The opacity of the UI drawn on top of the image, between 0.0 and 1.0.
|
||||
pub ui_opacity: f32,
|
||||
}
|
||||
|
||||
impl Default for BackgroundImageSettings {
|
||||
@@ -41,7 +42,6 @@ impl Default for BackgroundImageSettings {
|
||||
path: None,
|
||||
fit: BackgroundImageFit::Cover,
|
||||
opacity: DEFAULT_BACKGROUND_IMAGE_OPACITY,
|
||||
ui_opacity: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +62,6 @@ impl Settings for BackgroundImageSettings {
|
||||
.opacity
|
||||
.map(|opacity| opacity.clamp(0.0, 1.0))
|
||||
.unwrap_or(DEFAULT_BACKGROUND_IMAGE_OPACITY),
|
||||
ui_opacity: background
|
||||
.ui_opacity
|
||||
.map(|opacity| opacity.clamp(0.0, 1.0))
|
||||
.unwrap_or(1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,8 +165,7 @@ impl Render for BackgroundImage {
|
||||
.size_full()
|
||||
.child(
|
||||
img(ImageSource::Render(image))
|
||||
.object_fit(object_fit)
|
||||
.opacity(self.settings.opacity),
|
||||
.object_fit(object_fit),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -351,7 +346,6 @@ mod tests {
|
||||
path: Some("~/background.png".to_string()),
|
||||
fit: Some(BackgroundImageFit::Contain),
|
||||
opacity: Some(0.25),
|
||||
ui_opacity: None,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9323,10 +9323,6 @@ impl Render for Workspace {
|
||||
workspace: &self.weak_self,
|
||||
};
|
||||
|
||||
let background_settings =
|
||||
background_image::BackgroundImageSettings::get_global(cx);
|
||||
let ui_opacity = background_settings.ui_opacity;
|
||||
|
||||
let ui = div()
|
||||
.size_full()
|
||||
.flex()
|
||||
@@ -9337,7 +9333,6 @@ impl Render for Workspace {
|
||||
.items_start()
|
||||
.text_color(colors.text)
|
||||
.overflow_hidden()
|
||||
.when(ui_opacity < 1.0, |div| div.opacity(ui_opacity))
|
||||
// Expose the title bar as an ARIA toolbar so region navigation
|
||||
// (FocusNextPart) can reach the top bar's controls and assistive
|
||||
// technology announces it as a toolbar. The contained controls form
|
||||
|
||||
Reference in new issue
Block a user