diff --git a/assets/settings/default.json b/assets/settings/default.json index 7349eb2eef..f88a8aa9c4 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -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": { diff --git a/crates/settings_content/src/background_image.rs b/crates/settings_content/src/background_image.rs index f40c102c96..6109b5d021 100644 --- a/crates/settings_content/src/background_image.rs +++ b/crates/settings_content/src/background_image.rs @@ -39,4 +39,12 @@ pub struct BackgroundImageSettingsContent { /// /// Default: 0.6 pub opacity: Option, + + /// 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, } diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 62ce3b3230..afb56f052e 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -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, + }), ] } diff --git a/crates/workspace/src/background_image.rs b/crates/workspace/src/background_image.rs index 39f68f057c..caeb8fe51d 100644 --- a/crates/workspace/src/background_image.rs +++ b/crates/workspace/src/background_image.rs @@ -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, }); }); }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 83bb654807..b1f4ce8d2e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -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) } }