From 83f9eba1d1d491efbd32630e55ec4cb4a9e70ced Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 16 Dec 2025 09:28:30 +0000 Subject: [PATCH 01/17] Set `Ranked` to `true` for `ManiaModCover` --- osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs index 3ebfcedfd1..f51a7774a5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Mods typeof(ManiaModFadeIn) }).ToArray(); - public override bool Ranked => false; + public override bool Ranked => true; public override bool ValidForFreestyleAsRequiredMod => false; From 8da1fde981953f286a044db71eafb005da83f8e2 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 04:08:13 -0500 Subject: [PATCH 02/17] Add osu!-style undo icon --- osu.Game/Graphics/OsuIcon.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 0cf2acadda..a02c611285 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -28,6 +28,7 @@ namespace osu.Game.Graphics public static IconUsage EditCircle => get(OsuIconMapping.EditCircle); public static IconUsage LeftCircle => get(OsuIconMapping.LeftCircle); public static IconUsage RightCircle => get(OsuIconMapping.RightCircle); + public static IconUsage Undo => get(OsuIconMapping.Undo); public static IconUsage Audio => get(OsuIconMapping.Audio); public static IconUsage Beatmap => get(OsuIconMapping.Beatmap); @@ -386,6 +387,9 @@ namespace osu.Game.Graphics [Description(@"twitter")] Twitter, + [Description(@"undo")] + Undo, + [Description(@"user-interface")] UserInterface, From 729cd722bde881a0d5184d4cb155089560b779ee Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 04:08:01 -0500 Subject: [PATCH 03/17] Expose common interface for form controls --- .../Graphics/UserInterfaceV2/FormCheckBox.cs | 17 ++++++++- .../Graphics/UserInterfaceV2/FormDropdown.cs | 32 ++++++++++++++++- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 20 +++++++++-- .../Graphics/UserInterfaceV2/FormTextBox.cs | 16 ++++++++- .../Graphics/UserInterfaceV2/IFormControl.cs | 35 +++++++++++++++++++ 5 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/IFormControl.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs index d4cd86010f..471168a17f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs @@ -1,10 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -19,7 +22,7 @@ using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { - public partial class FormCheckBox : CompositeDrawable, IHasCurrentValue + public partial class FormCheckBox : CompositeDrawable, IHasCurrentValue, IFormControl { public Bindable Current { @@ -109,6 +112,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 updateState(); playSamples(); background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint); + + ValueChanged?.Invoke(); }); current.BindDisabledChanged(_ => updateState(), true); } @@ -157,5 +162,15 @@ namespace osu.Game.Graphics.UserInterfaceV2 BorderColour = colourProvider.Light4; } } + + public IEnumerable FilterTerms => Caption.Yield(); + + public event Action? ValueChanged; + + public bool IsValueDefault => Current.IsDefault; + + public void SetValueDefault() => Current.SetDefault(); + + public bool IsDisabled => Current.Disabled; } } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs index d47b9ac73d..7d21db1455 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -17,7 +20,7 @@ using osuTK; namespace osu.Game.Graphics.UserInterfaceV2 { - public partial class FormDropdown : OsuDropdown + public partial class FormDropdown : OsuDropdown, IFormControl { /// /// Caption describing this slider bar, displayed on top of the controls. @@ -40,6 +43,31 @@ namespace osu.Game.Graphics.UserInterfaceV2 header.HintText = HintText; } + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(_ => ValueChanged?.Invoke()); + } + + public virtual IEnumerable FilterTerms + { + get + { + yield return Caption; + + foreach (var item in Items) + yield return item?.ToString() ?? string.Empty; + } + } + + public event Action? ValueChanged; + + public bool IsValueDefault => Current.IsDefault; + + public void SetValueDefault() => Current.SetDefault(); + + public bool IsDisabled => Current.Disabled; + protected override DropdownHeader CreateHeader() => header = new FormDropdownHeader { Dropdown = this, @@ -243,6 +271,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 public partial class FormEnumDropdown : FormDropdown where T : struct, Enum { + public override IEnumerable FilterTerms => base.FilterTerms.Concat(Items.Select(i => i.GetLocalisableDescription())); + public FormEnumDropdown() { Items = Enum.GetValues(); diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 8ebaf48ed6..ef8227c12e 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Globalization; using System.Numerics; using osu.Framework.Allocation; @@ -22,7 +23,7 @@ using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { - public partial class FormSliderBar : CompositeDrawable, IHasCurrentValue + public partial class FormSliderBar : CompositeDrawable, IHasCurrentValue, IFormControl where T : struct, INumber, IMinMaxValue { public Bindable Current @@ -194,7 +195,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 slider.IsDragging.BindValueChanged(_ => updateState()); slider.Focused.BindValueChanged(_ => updateState()); - current.ValueChanged += e => currentNumberInstantaneous.Value = e.NewValue; + current.ValueChanged += e => + { + currentNumberInstantaneous.Value = e.NewValue; + ValueChanged?.Invoke(); + }; + current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v; current.MaxValueChanged += v => currentNumberInstantaneous.MaxValue = v; current.PrecisionChanged += v => currentNumberInstantaneous.Precision = v; @@ -475,5 +481,15 @@ namespace osu.Game.Graphics.UserInterfaceV2 return true; } } + + public IEnumerable FilterTerms => new[] { Caption, HintText }; + + public event Action? ValueChanged; + + public bool IsValueDefault => Current.IsDefault; + + public void SetValueDefault() => Current.SetDefault(); + + public bool IsDisabled => Current.Disabled; } } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 973419310c..41ea5242bd 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -20,7 +22,7 @@ using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { - public partial class FormTextBox : CompositeDrawable, IHasCurrentValue + public partial class FormTextBox : CompositeDrawable, IHasCurrentValue, IFormControl { public Bindable Current { @@ -157,6 +159,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 focusManager = GetContainingFocusManager()!; textBox.Focused.BindValueChanged(_ => updateState()); + + current.BindValueChanged(_ => ValueChanged?.Invoke()); current.BindDisabledChanged(_ => updateState(), true); } @@ -247,5 +251,15 @@ namespace osu.Game.Graphics.UserInterfaceV2 OnInputError?.Invoke(); } } + + public event Action? ValueChanged; + + public bool IsValueDefault => current.IsDefault; + + public void SetValueDefault() => current.SetDefault(); + + public bool IsDisabled => current.Disabled; + + public IEnumerable FilterTerms => Caption.Yield(); } } diff --git a/osu.Game/Graphics/UserInterfaceV2/IFormControl.cs b/osu.Game/Graphics/UserInterfaceV2/IFormControl.cs new file mode 100644 index 0000000000..c967ac76b0 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/IFormControl.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + /// + /// Represents an interface for all form controls. + /// + public interface IFormControl : IDrawable, IHasFilterTerms + { + /// + /// Invoked when the value of the control has changed. + /// + event Action ValueChanged; + + /// + /// Whether the value of this control is in a default state. + /// + bool IsValueDefault { get; } + + /// + /// If enabled, resets the control to its default state. + /// + void SetValueDefault(); + + /// + /// Whether the control is currently disabled. + /// + bool IsDisabled { get; } + } +} From 78043fa78237fb05ee40c85ce5b9bdc9c60fad80 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 04:09:02 -0500 Subject: [PATCH 04/17] Implement new settings item component --- osu.Game/Overlays/Settings/SettingsItemV2.cs | 176 ++++++++++++++++++ osu.Game/Overlays/Settings/SettingsNote.cs | 117 ++++++++++++ .../Settings/SettingsRevertToDefaultButton.cs | 95 ++++++++++ osu.Game/Overlays/SettingsPanel.cs | 3 + 4 files changed, 391 insertions(+) create mode 100644 osu.Game/Overlays/Settings/SettingsItemV2.cs create mode 100644 osu.Game/Overlays/Settings/SettingsNote.cs create mode 100644 osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs diff --git a/osu.Game/Overlays/Settings/SettingsItemV2.cs b/osu.Game/Overlays/Settings/SettingsItemV2.cs new file mode 100644 index 0000000000..21df39df38 --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsItemV2.cs @@ -0,0 +1,176 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Overlays.Settings +{ + public sealed partial class SettingsItemV2 : CompositeDrawable, ISettingsItem, IConditionalFilterable + { + private readonly IFormControl control; + private readonly SettingsRevertToDefaultButton revertButton; + + private readonly BindableBool controlDefault = new BindableBool(true); + private readonly BindableBool controlEnabled = new BindableBool(true); + + /// + /// Whether a revert-to-default button should be displayed. + /// + public bool ShowDefaultRevertButton { get; init; } = true; + + /// + /// A note to display underneath the setting. + /// + public readonly Bindable Note = new Bindable(); + + public SettingsItemV2(IFormControl control) + { + this.control = control; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS_RIGHT }, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + revertButton = new SettingsRevertToDefaultButton + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Action = ApplyDefault, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = (Drawable)control, + } + } + }, + new SettingsNote + { + RelativeSizeAxes = Axes.X, + Current = { BindTarget = Note }, + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + controlDefault.Value = control.IsValueDefault; + controlEnabled.Value = !control.IsDisabled; + + controlDefault.BindValueChanged(_ => updateDefaultState()); + controlEnabled.BindValueChanged(_ => updateDefaultState(), true); + FinishTransforms(true); + } + + private void updateDefaultState() + { + bool showRevertButton = !controlDefault.Value && controlEnabled.Value && ShowDefaultRevertButton; + + if (showRevertButton) + revertButton.Show(); + else + revertButton.Hide(); + } + + protected override void Update() + { + base.Update(); + controlDefault.Value = control.IsValueDefault; + controlEnabled.Value = !control.IsDisabled; + } + + #region ISettingsItem + + public bool HasClassicDefault { get; init; } + + public void ApplyClassicDefault() + { + // will be removed soon. + throw new NotSupportedException(); + } + + public void ApplyDefault() + { + if (!control.IsDisabled) + control.SetValueDefault(); + } + + public event Action SettingChanged + { + add => control.ValueChanged += value; + remove => control.ValueChanged -= value; + } + + #endregion + + #region Filtering + + public const string CLASSIC_DEFAULT_SEARCH_TERM = @"has-classic-default"; + + public IEnumerable Keywords { get; init; } = Enumerable.Empty(); + + public IEnumerable FilterTerms + { + get + { + var filterTerms = new List(Keywords.Select(k => (LocalisableString)k)); + filterTerms.AddRange(control.FilterTerms); + + if (HasClassicDefault) + filterTerms.Add(CLASSIC_DEFAULT_SEARCH_TERM); + + return filterTerms; + } + } + + private bool matchingFilter = true; + + public bool MatchingFilter + { + get => matchingFilter; + set + { + bool wasPresent = IsPresent; + + matchingFilter = value; + + if (IsPresent != wasPresent) + Invalidate(Invalidation.Presence); + } + } + + public override bool IsPresent => base.IsPresent && MatchingFilter; + + public bool FilteringActive { get; set; } + + public BindableBool CanBeShown { get; } = new BindableBool(true); + + IBindable IConditionalFilterable.CanBeShown => CanBeShown; + + #endregion + } +} diff --git a/osu.Game/Overlays/Settings/SettingsNote.cs b/osu.Game/Overlays/Settings/SettingsNote.cs new file mode 100644 index 0000000000..5ddbfa9fcd --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsNote.cs @@ -0,0 +1,117 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Settings +{ + public sealed partial class SettingsNote : CompositeDrawable + { + public readonly Bindable Current = new Bindable(); + + private Box background = null!; + private OsuTextFlowContainer text = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeDuration = 300; + AutoSizeEasing = Easing.OutQuint; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Top = 5, Bottom = 5 }, + Child = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + CornerRadius = 5, + CornerExponent = 2.5f, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + text = new OsuTextFlowContainer(s => s.Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold)) + { + Padding = new MarginPadding(8), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(_ => updateDisplay(), true); + FinishTransforms(true); + } + + private void updateDisplay() + { + ClearTransforms(); + + if (Current.Value == null) + { + AutoSizeAxes = Axes.None; + this.ResizeHeightTo(0, 300, Easing.OutQuint); + this.FadeOut(250, Easing.OutQuint); + return; + } + + AutoSizeAxes = Axes.Y; + this.FadeIn(250, Easing.OutQuint); + + switch (Current.Value.Type) + { + case Type.Informational: + background.Colour = colourProvider.Dark2; + text.Colour = colourProvider.Content2; + break; + + case Type.Warning: + background.Colour = colours.Orange1; + text.Colour = colourProvider.Background5; + break; + + case Type.Critical: + background.Colour = colours.Red1; + text.Colour = colourProvider.Background5; + break; + } + + text.Text = Current.Value.Text; + } + + public record Data(LocalisableString Text, Type Type); + + public enum Type + { + Informational, + Warning, + Critical, + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs b/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs new file mode 100644 index 0000000000..305a30a2f5 --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Overlays.Settings +{ + public partial class SettingsRevertToDefaultButton : OsuClickableContainer + { + public const float WIDTH = 32; + + public float IconSize { get; init; } = 14; + + private Box background = null!; + private SpriteIcon spriteIcon = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. + public override bool AcceptsFocus => true; + + public SettingsRevertToDefaultButton() + { + Size = new Vector2(WIDTH, 50); + } + + [BackgroundDependencyLoader] + private void load() + { + Masking = true; + CornerRadius = 5; + CornerExponent = 2.5f; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3, + }, + spriteIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colourProvider.Light1, + Icon = OsuIcon.Undo, + Margin = new MarginPadding { Left = 12, Right = 5 }, + Size = new Vector2(IconSize), + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Enabled.BindValueChanged(_ => updateDisplay(), true); + } + + protected override bool OnHover(HoverEvent e) + { + updateDisplay(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateDisplay(); + base.OnHoverLost(e); + } + + public override void Show() + { + this.FadeIn().MoveToX(WIDTH - 10, 300, Easing.OutQuint); + } + + public override void Hide() + { + this.MoveToX(0, 300, Easing.OutQuint).Then().FadeOut(); + } + + private void updateDisplay() + { + spriteIcon.FadeColour(IsHovered ? colourProvider.Content2 : colourProvider.Light1, 300, Easing.OutQuint); + background.FadeColour(IsHovered ? colourProvider.Background2 : colourProvider.Background3, 300, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 9b268c573f..8fd12e1e13 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -31,6 +31,9 @@ namespace osu.Game.Overlays { public const float CONTENT_MARGINS = 20; + // extra margin to give room to the revert-to-default button in settings controls. + public const float CONTENT_MARGINS_RIGHT = 30; + public const float TRANSITION_LENGTH = 600; private const float sidebar_width = SettingsSidebar.EXPANDED_WIDTH; From 7a32e0eebf74b8e84a48193d9e600f6d3566499d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 04:09:06 -0500 Subject: [PATCH 05/17] Add test coverage --- .../Settings/TestSceneSettingsItemV2.cs | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs new file mode 100644 index 0000000000..4476c32e9d --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs @@ -0,0 +1,263 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; +using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.Settings +{ + public partial class TestSceneSettingsItemV2 : ThemeComparisonTestScene + { + private readonly Bindable note = new Bindable(); + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private FormSliderBar sliderBar = null!; + + private SearchContainer searchContainer = null!; + + public TestSceneSettingsItemV2() + : base(false) + { + } + + protected override Drawable CreateContent() + { + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new BackgroundBox + { + RelativeSizeAxes = Axes.Both, + }, + new OsuContextMenuContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 400, + RelativeSizeAxes = Axes.Y, + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = searchContainer = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(7), + Padding = new MarginPadding { Vertical = 10 }, + Children = new[] + { + new SettingsItemV2(new FormTextBox + { + Caption = "Artist", + HintText = "Poot artist here!", + PlaceholderText = "Here is an artist", + Current = { Value = string.Empty, Default = string.Empty } + }), + new SettingsItemV2(new FormTextBox + { + Caption = "Artist", + HintText = "Poot artist here!", + PlaceholderText = "Here is an artist", + Current = { Value = string.Empty, Default = string.Empty, Disabled = true } + }), + new SettingsItemV2(new FormNumberBox(allowDecimals: true) + { + Caption = "Number", + HintText = "Insert your favourite number", + PlaceholderText = "Mine is 42!", + Current = { Value = string.Empty, Default = string.Empty } + }), + new SettingsItemV2(new FormCheckBox + { + Caption = EditorSetupStrings.LetterboxDuringBreaks, + HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, + }) + { + Note = { BindTarget = note }, + }, + new SettingsItemV2(new FormCheckBox + { + Caption = EditorSetupStrings.LetterboxDuringBreaks, + HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, + Current = { Disabled = true }, + }), + new SettingsItemV2(new FormCheckBox + { + Caption = EditorSetupStrings.LetterboxDuringBreaks, + HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, + Current = { Value = true, Disabled = true }, + }), + new SettingsItemV2(new FormEnumDropdown + { + Caption = EditorSetupStrings.EnableCountdown, + HintText = EditorSetupStrings.CountdownDescription, + }), + new SettingsItemV2(new FormEnumDropdown + { + Caption = EditorSetupStrings.EnableCountdown, + HintText = EditorSetupStrings.CountdownDescription, + Current = { Disabled = true }, + }), + new SettingsItemV2(new FormEnumDropdown + { + Caption = "Dropdown with many items", + HintText = EditorSetupStrings.CountdownDescription, + }) + { + Note = { BindTarget = note }, + }, + new SettingsItemV2(sliderBar = new FormSliderBar + { + Caption = "Slider", + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Value = 5, + Precision = 0.1f, + }, + }), + new SettingsItemV2(new FormSliderBar + { + Caption = "Slider", + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Value = 5, + Precision = 0.1f, + Disabled = true, + }, + TransferValueOnCommit = true, + }), + new SettingsItemV2(new FormSliderBar + { + Caption = "Slider without revert button", + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Value = 5, + Precision = 0.1f, + }, + }) + { + ShowDefaultRevertButton = false + }, + new SettingsItemV2(new FormSliderBar + { + Caption = "Slider with classic default", + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Value = 5, + Precision = 0.1f, + }, + }) + { + HasClassicDefault = true, + }, + }, + }, + }, + } + }, + }, + }; + } + + [Test] + public void TestDisplay() + { + AddStep("display", () => CreateThemedContent(OverlayColourScheme.Purple)); + } + + [Test] + public void TestNote() + { + AddStep("set informational note", () => note.Value = new SettingsNote.Data(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen.ToString(), SettingsNote.Type.Informational)); + AddStep("set warning note", + () => note.Value = new SettingsNote.Data( + "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. “2x refresh rate” is recommended.", + SettingsNote.Type.Warning)); + AddStep("set critical note", + () => note.Value = new SettingsNote.Data( + "You have done something so horrible in the game settings to the point we have invented a new note type for this. Look at it, it's in red. It's worse than yellow.", + SettingsNote.Type.Critical)); + AddStep("clear note", () => note.Value = null); + } + + [Test] + public void TestFilter() + { + AddStep("set classic filter", () => searchContainer.SearchTerm = SettingsItemV2.CLASSIC_DEFAULT_SEARCH_TERM); + AddStep("set no filter", () => searchContainer.SearchTerm = string.Empty); + } + + /// + /// Ensures that the reset to default button uses the correct implementation of IsDefault to determine whether it should be shown or not. + /// Values have been chosen so that after being set, Value != Default (but they are close enough that the difference is negligible compared to Precision). + /// + [TestCase(4.2f)] + [TestCase(9.9f)] + public void TestRestoreDefaultValueButtonPrecision(float initialValue) + { + BindableFloat current = null!; + SettingsRevertToDefaultButton revertToDefaultButton = null!; + + AddStep("set current bindable", () => sliderBar.Current = current = new BindableFloat(initialValue) + { + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + }); + + AddStep("retrieve restore default button", () => revertToDefaultButton = sliderBar.FindClosestParent().ChildrenOfType().Single()); + + AddAssert("restore button hidden", () => revertToDefaultButton.X == 0); + + AddStep("change value to next closest", () => sliderBar.Current.Value += current.Precision * 0.6f); + AddUntilStep("restore button shown", () => revertToDefaultButton.X > 0); + + AddStep("restore default", () => sliderBar.Current.SetDefault()); + AddUntilStep("restore button hidden", () => revertToDefaultButton.X == 0); + } + + private partial class BackgroundBox : Box + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Background4; + } + } + } +} From 90d76154328883b40f8d670589a35f4bc0175064 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 04:11:59 -0500 Subject: [PATCH 06/17] Limit form dropdown height by default --- osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs index 7d21db1455..021df4c2c9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs @@ -32,6 +32,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 /// public LocalisableString HintText { get; init; } + /// + /// The maximum height of the dropdown's menu. + /// By default, this is set to 200px high. Set to to remove such limit. + /// + public float MaxHeight { get; set; } = 200; + private FormDropdownHeader header = null!; [BackgroundDependencyLoader] @@ -73,7 +79,10 @@ namespace osu.Game.Graphics.UserInterfaceV2 Dropdown = this, }; - protected override DropdownMenu CreateMenu() => new FormDropdownMenu(); + protected override DropdownMenu CreateMenu() => new FormDropdownMenu + { + MaxHeight = MaxHeight, + }; private partial class FormDropdownHeader : DropdownHeader { From f5bd888078f63383d94cee3363be010de39f1750 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 05:50:35 -0500 Subject: [PATCH 07/17] Remove `Value` from `{Is,Set}ValueDefault` --- osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs | 4 ++-- osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs | 4 ++-- osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs | 4 ++-- osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs | 4 ++-- osu.Game/Graphics/UserInterfaceV2/IFormControl.cs | 4 ++-- osu.Game/Overlays/Settings/SettingsItemV2.cs | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs index 471168a17f..586f6546ab 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs @@ -167,9 +167,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 public event Action? ValueChanged; - public bool IsValueDefault => Current.IsDefault; + public bool IsDefault => Current.IsDefault; - public void SetValueDefault() => Current.SetDefault(); + public void SetDefault() => Current.SetDefault(); public bool IsDisabled => Current.Disabled; } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs index 021df4c2c9..852ff4bbad 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs @@ -68,9 +68,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 public event Action? ValueChanged; - public bool IsValueDefault => Current.IsDefault; + public bool IsDefault => Current.IsDefault; - public void SetValueDefault() => Current.SetDefault(); + public void SetDefault() => Current.SetDefault(); public bool IsDisabled => Current.Disabled; diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index ef8227c12e..5e843fd751 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -486,9 +486,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 public event Action? ValueChanged; - public bool IsValueDefault => Current.IsDefault; + public bool IsDefault => Current.IsDefault; - public void SetValueDefault() => Current.SetDefault(); + public void SetDefault() => Current.SetDefault(); public bool IsDisabled => Current.Disabled; } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 41ea5242bd..4981557e37 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -254,9 +254,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 public event Action? ValueChanged; - public bool IsValueDefault => current.IsDefault; + public bool IsDefault => current.IsDefault; - public void SetValueDefault() => current.SetDefault(); + public void SetDefault() => current.SetDefault(); public bool IsDisabled => current.Disabled; diff --git a/osu.Game/Graphics/UserInterfaceV2/IFormControl.cs b/osu.Game/Graphics/UserInterfaceV2/IFormControl.cs index c967ac76b0..97630a649a 100644 --- a/osu.Game/Graphics/UserInterfaceV2/IFormControl.cs +++ b/osu.Game/Graphics/UserInterfaceV2/IFormControl.cs @@ -20,12 +20,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 /// /// Whether the value of this control is in a default state. /// - bool IsValueDefault { get; } + bool IsDefault { get; } /// /// If enabled, resets the control to its default state. /// - void SetValueDefault(); + void SetDefault(); /// /// Whether the control is currently disabled. diff --git a/osu.Game/Overlays/Settings/SettingsItemV2.cs b/osu.Game/Overlays/Settings/SettingsItemV2.cs index 21df39df38..622df50b91 100644 --- a/osu.Game/Overlays/Settings/SettingsItemV2.cs +++ b/osu.Game/Overlays/Settings/SettingsItemV2.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Settings { base.LoadComplete(); - controlDefault.Value = control.IsValueDefault; + controlDefault.Value = control.IsDefault; controlEnabled.Value = !control.IsDisabled; controlDefault.BindValueChanged(_ => updateDefaultState()); @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Settings protected override void Update() { base.Update(); - controlDefault.Value = control.IsValueDefault; + controlDefault.Value = control.IsDefault; controlEnabled.Value = !control.IsDisabled; } @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Settings public void ApplyDefault() { if (!control.IsDisabled) - control.SetValueDefault(); + control.SetDefault(); } public event Action SettingChanged From 094860bfea380eec0ce876dc0339e906cc142bb2 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 06:17:07 -0500 Subject: [PATCH 08/17] Define delegate for applying classic defaults --- osu.Game/Overlays/Settings/SettingsItemV2.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItemV2.cs b/osu.Game/Overlays/Settings/SettingsItemV2.cs index 622df50b91..bd246211b6 100644 --- a/osu.Game/Overlays/Settings/SettingsItemV2.cs +++ b/osu.Game/Overlays/Settings/SettingsItemV2.cs @@ -105,14 +105,26 @@ namespace osu.Game.Overlays.Settings #region ISettingsItem - public bool HasClassicDefault { get; init; } + public bool HasClassicDefault { get; private set; } - public void ApplyClassicDefault() + private Action? applyClassicDefault; + + /// + /// If set, this setting is considered as having a "classic" default value, + /// and this is the function for overwriting the control with that value. + /// + public Action? ApplyClassicDefault { - // will be removed soon. - throw new NotSupportedException(); + get => applyClassicDefault; + set + { + applyClassicDefault = value; + HasClassicDefault = true; + } } + void ISettingsItem.ApplyClassicDefault() => ApplyClassicDefault?.Invoke(); + public void ApplyDefault() { if (!control.IsDisabled) From 61874e59e03f7c393a13435534a336b618e39aa4 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 06:17:48 -0500 Subject: [PATCH 09/17] Extend classic default test coverage --- .../Visual/Settings/TestSceneSettingsItemV2.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs index 4476c32e9d..bdfe8019bc 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -30,6 +31,7 @@ namespace osu.Game.Tests.Visual.Settings private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); private FormSliderBar sliderBar = null!; + private FormSliderBar classicSliderBar = null!; private SearchContainer searchContainer = null!; @@ -171,7 +173,7 @@ namespace osu.Game.Tests.Visual.Settings { ShowDefaultRevertButton = false }, - new SettingsItemV2(new FormSliderBar + new SettingsItemV2(classicSliderBar = new FormSliderBar { Caption = "Slider with classic default", Current = new BindableFloat @@ -183,7 +185,7 @@ namespace osu.Game.Tests.Visual.Settings }, }) { - HasClassicDefault = true, + ApplyClassicDefault = () => classicSliderBar.Current.Value = 2, }, }, }, @@ -216,10 +218,16 @@ namespace osu.Game.Tests.Visual.Settings } [Test] - public void TestFilter() + public void TestClassicDefault() { + AddStep("modify irrelevant setting", () => sliderBar.Current.Value = 4); + AddStep("apply classic defaults", () => this.ChildrenOfType().Where(i => i.HasClassicDefault).ForEach(s => s.ApplyClassicDefault())); + AddStep("apply regular defaults", () => this.ChildrenOfType().Where(i => i.HasClassicDefault).ForEach(s => s.ApplyDefault())); AddStep("set classic filter", () => searchContainer.SearchTerm = SettingsItemV2.CLASSIC_DEFAULT_SEARCH_TERM); + AddStep("apply classic defaults", () => this.ChildrenOfType().Where(i => i.HasClassicDefault).ForEach(s => s.ApplyClassicDefault())); + AddStep("apply regular defaults", () => this.ChildrenOfType().Where(i => i.HasClassicDefault).ForEach(s => s.ApplyDefault())); AddStep("set no filter", () => searchContainer.SearchTerm = string.Empty); + AddAssert("irrelevant setting left out", () => sliderBar.Current.Value, () => Is.EqualTo(4)); } /// From de0c191c475a0a8c5a05337556a3636a532a3a70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Dec 2025 20:53:42 +0900 Subject: [PATCH 10/17] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a2dabe7a9f..749afd8d5e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From a4215e044206c0f67ed5cce09d1268a71c8dfa2e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 18:56:18 -0500 Subject: [PATCH 11/17] Add tooltip to revert-to-default button --- osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs b/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs index 305a30a2f5..58e511341b 100644 --- a/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs +++ b/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs @@ -6,8 +6,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Localisation; using osuTK; namespace osu.Game.Overlays.Settings @@ -64,6 +66,8 @@ namespace osu.Game.Overlays.Settings Enabled.BindValueChanged(_ => updateDisplay(), true); } + public override LocalisableString TooltipText => CommonStrings.RevertToDefault; + protected override bool OnHover(HoverEvent e) { updateDisplay(); From 4fee1547e328884aa991b6539b8e60c27594055c Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 19:48:42 -0500 Subject: [PATCH 12/17] Use menu item labels as filter terms instead --- osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs index 852ff4bbad..a12d166f56 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -61,8 +59,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 { yield return Caption; - foreach (var item in Items) - yield return item?.ToString() ?? string.Empty; + foreach (var item in MenuItems) + yield return item.Text.Value; } } @@ -280,8 +278,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 public partial class FormEnumDropdown : FormDropdown where T : struct, Enum { - public override IEnumerable FilterTerms => base.FilterTerms.Concat(Items.Select(i => i.GetLocalisableDescription())); - public FormEnumDropdown() { Items = Enum.GetValues(); From a82d0c3b5142cfad8a49780a47c1e608eefa133e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 20:01:36 -0500 Subject: [PATCH 13/17] Rename property to have consistent name --- osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs | 2 +- osu.Game/Overlays/Settings/SettingsItemV2.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs index bdfe8019bc..043eadf370 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItemV2.cs @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Settings }, }) { - ShowDefaultRevertButton = false + ShowRevertToDefaultButton = false }, new SettingsItemV2(classicSliderBar = new FormSliderBar { diff --git a/osu.Game/Overlays/Settings/SettingsItemV2.cs b/osu.Game/Overlays/Settings/SettingsItemV2.cs index bd246211b6..1f947d2898 100644 --- a/osu.Game/Overlays/Settings/SettingsItemV2.cs +++ b/osu.Game/Overlays/Settings/SettingsItemV2.cs @@ -21,9 +21,9 @@ namespace osu.Game.Overlays.Settings private readonly BindableBool controlEnabled = new BindableBool(true); /// - /// Whether a revert-to-default button should be displayed. + /// Whether a revert button should be displayed when the control is modified away from default state. /// - public bool ShowDefaultRevertButton { get; init; } = true; + public bool ShowRevertToDefaultButton { get; init; } = true; /// /// A note to display underneath the setting. @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Settings private void updateDefaultState() { - bool showRevertButton = !controlDefault.Value && controlEnabled.Value && ShowDefaultRevertButton; + bool showRevertButton = !controlDefault.Value && controlEnabled.Value && ShowRevertToDefaultButton; if (showRevertButton) revertButton.Show(); From 276757315f0197cdf418ba35cd26237974fa511e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 20:03:38 -0500 Subject: [PATCH 14/17] Simplify classic default flow --- osu.Game/Overlays/Settings/SettingsItemV2.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItemV2.cs b/osu.Game/Overlays/Settings/SettingsItemV2.cs index 1f947d2898..084aa1ad1f 100644 --- a/osu.Game/Overlays/Settings/SettingsItemV2.cs +++ b/osu.Game/Overlays/Settings/SettingsItemV2.cs @@ -105,23 +105,13 @@ namespace osu.Game.Overlays.Settings #region ISettingsItem - public bool HasClassicDefault { get; private set; } - - private Action? applyClassicDefault; + public bool HasClassicDefault => ApplyClassicDefault != null; /// /// If set, this setting is considered as having a "classic" default value, /// and this is the function for overwriting the control with that value. /// - public Action? ApplyClassicDefault - { - get => applyClassicDefault; - set - { - applyClassicDefault = value; - HasClassicDefault = true; - } - } + public Action? ApplyClassicDefault { get; set; } void ISettingsItem.ApplyClassicDefault() => ApplyClassicDefault?.Invoke(); From c475b4bc4bf3e319c48d3f569737693da0c21e9e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 18 Dec 2025 20:04:02 -0500 Subject: [PATCH 15/17] Add explanatory note for `ClearTransforms` usage --- osu.Game/Overlays/Settings/SettingsNote.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/SettingsNote.cs b/osu.Game/Overlays/Settings/SettingsNote.cs index 5ddbfa9fcd..3552e32d4e 100644 --- a/osu.Game/Overlays/Settings/SettingsNote.cs +++ b/osu.Game/Overlays/Settings/SettingsNote.cs @@ -71,6 +71,7 @@ namespace osu.Game.Overlays.Settings private void updateDisplay() { + // Explicitly use ClearTransforms to clear any existing auto-size transform before modifying size / flag. ClearTransforms(); if (Current.Value == null) From f273163e58053a21c21ece9ecf8866ca7b764880 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 19 Dec 2025 02:24:42 -0500 Subject: [PATCH 16/17] Improve revert button animation --- osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs b/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs index 58e511341b..fd95277446 100644 --- a/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs +++ b/osu.Game/Overlays/Settings/SettingsRevertToDefaultButton.cs @@ -82,12 +82,12 @@ namespace osu.Game.Overlays.Settings public override void Show() { - this.FadeIn().MoveToX(WIDTH - 10, 300, Easing.OutQuint); + this.FadeIn().MoveToX(WIDTH - 10, 200, Easing.OutElasticQuarter); } public override void Hide() { - this.MoveToX(0, 300, Easing.OutQuint).Then().FadeOut(); + this.MoveToX(0, 120, Easing.OutExpo).Then().FadeOut(); } private void updateDisplay() From 0787345a84e62bf131ec5ef6efef7996e6f8d1da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Dec 2025 22:06:39 +0900 Subject: [PATCH 17/17] Adjust colour of inactive slider bar --- osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 5e843fd751..5487f27254 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -440,7 +440,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 private void updateState() { rightBox.Colour = colourProvider.Background6; - leftBox.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; + leftBox.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Highlight1.Opacity(0.3f); nub.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4; }