workspace: Fix foreground hang and let background show through UI

- Move the background image load and decode off the foreground thread
  (they were being polled there and tripped hang detection)
- Add background.ui_opacity: scales the opacity of everything drawn on
  top of the image so it shows through panels
This commit is contained in:
yenru0 committed 2026-09-03 13:52:27 +09:00
1 parent 46d3b70e76
commit ada1148914
5 files changed
+63 -8

No files matched your search

+3
View File
@@ -606,6 +606,9 @@
"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,
},
// Scrollbar related settings
"scrollbar": {
@@ -39,4 +39,12 @@ pub struct BackgroundImageSettingsContent {
///
/// Default: 0.6
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>,
}
+20 -1
View File
@@ -1530,7 +1530,7 @@ fn appearance_page() -> SettingsPage {
]
}
fn background_image_section() -> [SettingsPageItem; 4] {
fn background_image_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Background Image"),
SettingsPageItem::SettingItem(SettingItem {
@@ -1590,6 +1590,25 @@ 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,
}),
]
}
+19 -3
View File
@@ -31,6 +31,8 @@ pub struct BackgroundImageSettings {
pub fit: BackgroundImageFit,
/// The opacity of the image, between 0.0 and 1.0.
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 {
@@ -39,6 +41,7 @@ impl Default for BackgroundImageSettings {
path: None,
fit: BackgroundImageFit::Cover,
opacity: DEFAULT_BACKGROUND_IMAGE_OPACITY,
ui_opacity: 1.0,
}
}
}
@@ -59,6 +62,10 @@ 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),
}
}
}
@@ -110,12 +117,20 @@ impl BackgroundImage {
let cache_dir = self.cache_dir.clone();
let executor = cx.background_executor().clone();
self._load_task = Some(cx.spawn(async move |this, cx| {
let image =
load_background_image(&fs, &path, &cache_dir, executor.clone()).await;
let fs = fs.clone();
let path = path.clone();
let path_for_check = path.clone();
let cache_dir = cache_dir.clone();
let load_task = executor
.clone()
.spawn(async move {
load_background_image(&fs, &path, &cache_dir, executor.clone()).await
});
let image = load_task.await;
this.update(cx, |this, cx| {
// Drop the result if the settings changed while loading.
if this.settings.path.as_deref() == Some(path.as_ref()) {
if this.settings.path.as_deref() == Some(path_for_check.as_ref()) {
this.image = image;
}
this._load_task = None;
@@ -336,6 +351,7 @@ mod tests {
path: Some("~/background.png".to_string()),
fit: Some(BackgroundImageFit::Contain),
opacity: Some(0.25),
ui_opacity: None,
});
});
});
+13 -4
View File
@@ -9323,9 +9323,11 @@ impl Render for Workspace {
workspace: &self.weak_self,
};
div()
.relative()
.child(self.background_image.clone())
let background_settings =
background_image::BackgroundImageSettings::get_global(cx);
let ui_opacity = background_settings.ui_opacity;
let ui = div()
.size_full()
.flex()
.flex_col()
@@ -9335,6 +9337,7 @@ 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
@@ -9742,7 +9745,13 @@ impl Render for Workspace {
parent.child(self.status_bar.clone())
})
.child(self.toast_layer.clone()),
)
);
div()
.relative()
.size_full()
.child(self.background_image.clone())
.child(ui)
}
}