settings_ui: Fix stepper buttons to Inactive Opacity to 0.1 increment adjustments (#40477)

Delvin and Danilo Leal created

Closes #40279

Release Notes:

- Fix stepper buttons (+/-) to the Inactive Opacity setting for 0.1
increment adjustments on settings UI

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

crates/settings/src/settings_content/editor.rs    | 25 ++++++++++++++++
crates/settings/src/settings_content/workspace.rs |  5 +-
crates/settings/src/vscode_import.rs              |  2 
crates/settings_ui/src/settings_ui.rs             |  1 
crates/ui_input/src/number_field.rs               | 26 ++++++++++++++++
crates/workspace/src/pane_group.rs                |  2 
crates/workspace/src/workspace_settings.rs        |  4 +-
7 files changed, 58 insertions(+), 7 deletions(-)

Detailed changes

crates/settings/src/settings_content/editor.rs 🔗

@@ -803,3 +803,28 @@ impl Display for MinimumContrast {
         write!(f, "{:.1}", self.0)
     }
 }
+
+/// Opacity of the inactive panes. 0 means transparent, 1 means opaque.
+///
+/// Valid range: 0.0 to 1.0
+/// Default: 1.0
+#[derive(
+    Clone,
+    Copy,
+    Debug,
+    Serialize,
+    Deserialize,
+    JsonSchema,
+    MergeFrom,
+    PartialEq,
+    PartialOrd,
+    derive_more::FromStr,
+)]
+#[serde(transparent)]
+pub struct InactiveOpacity(pub f32);
+
+impl Display for InactiveOpacity {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{:.1}", self.0)
+    }
+}

crates/settings/src/settings_content/workspace.rs 🔗

@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 use settings_macros::MergeFrom;
 
-use crate::{DockPosition, DockSide, ScrollbarSettingsContent, ShowIndentGuides};
+use crate::{DockPosition, DockSide, InactiveOpacity, ScrollbarSettingsContent, ShowIndentGuides};
 
 #[skip_serializing_none]
 #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
@@ -256,7 +256,8 @@ pub struct ActivePaneModifiers {
     /// Values are clamped to the [0.0, 1.0] range.
     ///
     /// Default: `1.0`
-    pub inactive_opacity: Option<f32>,
+    #[schemars(range(min = 0.0, max = 1.0))]
+    pub inactive_opacity: Option<InactiveOpacity>,
 }
 
 #[derive(

crates/settings/src/vscode_import.rs 🔗

@@ -843,7 +843,7 @@ impl VsCodeSettings {
         {
             Some(ActivePaneModifiers {
                 border_size: None,
-                inactive_opacity: Some(opacity),
+                inactive_opacity: Some(InactiveOpacity(opacity)),
             })
         } else {
             None

crates/settings_ui/src/settings_ui.rs 🔗

@@ -418,6 +418,7 @@ fn init_renderers(cx: &mut App) {
         .add_basic_renderer::<NonZeroU32>(render_number_field)
         .add_basic_renderer::<settings::CodeFade>(render_number_field)
         .add_basic_renderer::<gpui::FontWeight>(render_number_field)
+        .add_basic_renderer::<settings::InactiveOpacity>(render_number_field)
         .add_basic_renderer::<settings::MinimumContrast>(render_number_field)
         .add_basic_renderer::<settings::ShowScrollbar>(render_dropdown)
         .add_basic_renderer::<settings::ScrollbarDiagnostics>(render_dropdown)

crates/ui_input/src/number_field.rs 🔗

@@ -8,7 +8,7 @@ use std::{
 use editor::{Editor, EditorStyle};
 use gpui::{ClickEvent, Entity, FocusHandle, Focusable, FontWeight, Modifiers};
 
-use settings::{CodeFade, MinimumContrast};
+use settings::{CodeFade, InactiveOpacity, MinimumContrast};
 use ui::prelude::*;
 
 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
@@ -79,6 +79,30 @@ impl NumberFieldType for settings::CodeFade {
     }
 }
 
+impl NumberFieldType for settings::InactiveOpacity {
+    fn default_step() -> Self {
+        InactiveOpacity(0.10)
+    }
+    fn large_step() -> Self {
+        InactiveOpacity(0.20)
+    }
+    fn small_step() -> Self {
+        InactiveOpacity(0.05)
+    }
+    fn min_value() -> Self {
+        InactiveOpacity(0.0)
+    }
+    fn max_value() -> Self {
+        InactiveOpacity(1.0)
+    }
+    fn saturating_add(self, rhs: Self) -> Self {
+        InactiveOpacity((self.0 + rhs.0).min(Self::max_value().0))
+    }
+    fn saturating_sub(self, rhs: Self) -> Self {
+        InactiveOpacity((self.0 - rhs.0).max(Self::min_value().0))
+    }
+}
+
 impl NumberFieldType for settings::MinimumContrast {
     fn default_step() -> Self {
         MinimumContrast(1.0)

crates/workspace/src/pane_group.rs 🔗

@@ -1306,7 +1306,7 @@ mod element {
             let overlay_opacity = WorkspaceSettings::get(None, cx)
                 .active_pane_modifiers
                 .inactive_opacity
-                .map(|val| val.clamp(0.0, 1.0))
+                .map(|val| val.0.clamp(0.0, 1.0))
                 .and_then(|val| (val <= 1.).then_some(val));
 
             let mut overlay_background = cx.theme().colors().editor_background;

crates/workspace/src/workspace_settings.rs 🔗

@@ -4,11 +4,11 @@ use crate::DockPosition;
 use collections::HashMap;
 use serde::Deserialize;
 pub use settings::AutosaveSetting;
-use settings::Settings;
 pub use settings::{
     BottomDockLayout, PaneSplitDirectionHorizontal, PaneSplitDirectionVertical,
     RestoreOnStartupBehavior,
 };
+use settings::{InactiveOpacity, Settings};
 
 pub struct WorkspaceSettings {
     pub active_pane_modifiers: ActivePanelModifiers,
@@ -50,7 +50,7 @@ pub struct ActivePanelModifiers {
     ///
     /// Default: `1.0`
     // TODO: make this not an option, it is never None
-    pub inactive_opacity: Option<f32>,
+    pub inactive_opacity: Option<InactiveOpacity>,
 }
 
 #[derive(Deserialize)]