Skip to content

Prepare form controls for use in settings#36116

Merged
peppy merged 14 commits intoppy:masterfrom
frenzibyte:new-settings/form-visuals
Dec 24, 2025
Merged

Prepare form controls for use in settings#36116
peppy merged 14 commits intoppy:masterfrom
frenzibyte:new-settings/form-visuals

Conversation

@frenzibyte
Copy link
Copy Markdown
Member

@frenzibyte frenzibyte commented Dec 23, 2025

This applies visual and audible changes, and adds new features for form controls to be ready for use in settings overlay. Changes are summarized as such:

  • All disabled form controls match sliders visually (dark background and greyed content).
  • Checkbox nubs are replaced with switch buttons.
  • Sliders properly support percentage and custom label and tooltip formatting display.
Before After
CleanShot 2025-12-22 at 20 27 19 CleanShot 2025-12-22 at 20 30 30

Video preview:

preview
CleanShot.2025-12-22.at.20.23.22.mp4

@frenzibyte frenzibyte added the type/cosmetic Only affects the game visually. Doesn't affect things working or not working. label Dec 23, 2025
@frenzibyte frenzibyte requested a review from a team December 23, 2025 02:13
Copy link
Copy Markdown
Collaborator

@bdach bdach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only interested in structure, will leave it to @peppy to assess visuals

@frenzibyte frenzibyte force-pushed the new-settings/form-visuals branch from 3856399 to cb32d2d Compare December 23, 2025 10:56
@bdach bdach requested a review from peppy December 23, 2025 11:16

private partial class FormSwitchButton : SwitchButton
{
// it doesn't make sense for this to show hover state or respond to input.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what "doesn't make sense" about this one? Every other form control has its nested element still respond to hover and show animations and tinting changes, except for this.

(I came here because I noticed the hover effect was not working in the first place, FWIW)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It felt odd to make it respond to hover if clicking inside and clicking outside of the switch does the exact same thing, but I don't feel strongly about it.

@peppy
Copy link
Copy Markdown
Member

peppy commented Dec 23, 2025

The new switch control didn't match the colouring of the slider bar, so I made it match:

diff --git a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs
index 78feee871d..886166d00b 100644
--- a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs
@@ -176,9 +176,6 @@ private void updateState()
 
         private partial class FormSwitchButton : SwitchButton
         {
-            // it doesn't make sense for this to show hover state or respond to input.
-            // FormCheckBox already handles all of that.
-            public override bool PropagatePositionalInputSubTree => false;
         }
     }
 }
diff --git a/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs
index fe72775bb5..7b577220b5 100644
--- a/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs
@@ -23,9 +23,9 @@ public partial class SwitchButton : Checkbox
         private const float padding = 1.25f;
 
         private readonly Box fill;
-        private readonly Container switchContainer;
-        private readonly Drawable switchCircle;
-        private readonly CircularContainer circularContainer;
+        private readonly Container nubContainer;
+        private readonly Drawable nub;
+        private readonly CircularContainer content;
 
         [Resolved]
         private OverlayColourProvider colourProvider { get; set; } = null!;
@@ -37,7 +37,7 @@ public SwitchButton()
         {
             Size = new Vector2(45, 20);
 
-            InternalChild = circularContainer = new CircularContainer
+            InternalChild = content = new CircularContainer
             {
                 RelativeSizeAxes = Axes.Both,
                 BorderColour = Color4.White,
@@ -55,15 +55,14 @@ public SwitchButton()
                     {
                         RelativeSizeAxes = Axes.Both,
                         Padding = new MarginPadding(border_thickness + padding),
-                        Child = switchContainer = new Container
+                        Child = nubContainer = new Container
                         {
                             RelativeSizeAxes = Axes.Both,
-                            Child = switchCircle = new CircularContainer
+                            Child = nub = new Circle
                             {
                                 RelativeSizeAxes = Axes.Both,
                                 FillMode = FillMode.Fit,
                                 Masking = true,
-                                Child = new Box { RelativeSizeAxes = Axes.Both }
                             }
                         }
                     }
@@ -90,7 +89,7 @@ protected override void LoadComplete()
 
         private void updateState()
         {
-            switchCircle.MoveToX(Current.Value ? switchContainer.DrawWidth - switchCircle.DrawWidth : 0, 200, Easing.OutQuint);
+            nub.MoveToX(Current.Value ? nubContainer.DrawWidth - nub.DrawWidth : 0, 200, Easing.OutQuint);
             fill.FadeTo(Current.Value ? 1 : 0, 250, Easing.OutQuint);
 
             updateColours();
@@ -120,32 +119,33 @@ protected override void OnUserChange(bool value)
 
         private void updateColours()
         {
-            ColourInfo targetSwitchColour;
-            ColourInfo targetBorderColour;
+            ColourInfo borderColour;
+            ColourInfo switchColour;
 
             if (Current.Disabled)
             {
-                if (Current.Value)
-                    targetBorderColour = colourProvider.Dark1.Opacity(0.5f);
-                else
-                    targetBorderColour = colourProvider.Background2.Opacity(0.5f);
-
-                targetSwitchColour = colourProvider.Dark1.Opacity(0.5f);
-                fill.Colour = colourProvider.Background5;
+                borderColour = colourProvider.Dark2;
+                switchColour = colourProvider.Dark1;
+                fill.Colour = colourProvider.Dark3;
             }
             else
             {
-                if (Current.Value)
-                    targetBorderColour = IsHovered ? colourProvider.Highlight1.Lighten(0.3f) : colourProvider.Highlight1;
-                else
-                    targetBorderColour = IsHovered ? colourProvider.Background1 : colourProvider.Background2;
+                bool hover = IsHovered && !Current.Disabled;
+
+                borderColour = hover ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Highlight1.Opacity(0.3f);
+                switchColour = hover ? colourProvider.Highlight1 : colourProvider.Light4;
+
+                if (!Current.Value)
+                {
+                    borderColour = borderColour.MultiplyAlpha(0.8f);
+                    switchColour = switchColour.MultiplyAlpha(0.8f);
+                }
 
-                targetSwitchColour = colourProvider.Highlight1;
-                fill.Colour = colourProvider.Background4;
+                fill.Colour = colourProvider.Background6;
             }
 
-            switchContainer.FadeColour(targetSwitchColour, 250, Easing.OutQuint);
-            circularContainer.TransformTo(nameof(BorderColour), targetBorderColour, 250, Easing.OutQuint);
+            nubContainer.FadeColour(switchColour, 250, Easing.OutQuint);
+            content.TransformTo(nameof(BorderColour), borderColour, 250, Easing.OutQuint);
         }
     }
 }

@frenzibyte please confirm you're okay with the change and commit if so. Note that this also re-enables the hover effects as I pointed out in my review.

@frenzibyte
Copy link
Copy Markdown
Member Author

I have few concerns with this change:

  • Representing the checked-on state with a darker background than disabled feels almost back-to-front to me.
  • The checkbox being checked-on while diasbled/non-interactive looks broken visually.

However, after playing around with it I couldn't come up with anything that's still on the same palette as the slider so I gave up on point number one. As for point number 2, I replaced the background colour Dark3 for Dark5 and it looks as such now:

Dark3 Dark5
CleanShot 2025-12-23 at 20 17 40 CleanShot 2025-12-23 at 20 15 51

I've pushed the changes above with that included.

@peppy
Copy link
Copy Markdown
Member

peppy commented Dec 24, 2025

@frenzibyte tests do not pass

@frenzibyte
Copy link
Copy Markdown
Member Author

frenzibyte commented Dec 24, 2025

Had to cache colour provider in tournaments client, and adjust at least the background colour in the setup screen to fit well after this change.

@peppy peppy self-requested a review December 24, 2025 16:42
@peppy peppy merged commit 2acc2a8 into ppy:master Dec 24, 2025
7 of 9 checks passed
@frenzibyte frenzibyte deleted the new-settings/form-visuals branch December 24, 2025 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL type/cosmetic Only affects the game visually. Doesn't affect things working or not working.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants