1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-18 14:10:33 +08:00

Merge branch 'master' into form-slider-bar-disabled

This commit is contained in:
Dean Herbert
2025-12-22 23:44:33 +09:00
Unverified
13 changed files with 797 additions and 9 deletions
@@ -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;
@@ -0,0 +1,271 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Extensions.IEnumerableExtensions;
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<SettingsNote.Data?> note = new Bindable<SettingsNote.Data?>();
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private FormSliderBar<float> sliderBar = null!;
private FormSliderBar<float> classicSliderBar = 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<CountdownType>
{
Caption = EditorSetupStrings.EnableCountdown,
HintText = EditorSetupStrings.CountdownDescription,
}),
new SettingsItemV2(new FormEnumDropdown<CountdownType>
{
Caption = EditorSetupStrings.EnableCountdown,
HintText = EditorSetupStrings.CountdownDescription,
Current = { Disabled = true },
}),
new SettingsItemV2(new FormEnumDropdown<Language>
{
Caption = "Dropdown with many items",
HintText = EditorSetupStrings.CountdownDescription,
})
{
Note = { BindTarget = note },
},
new SettingsItemV2(sliderBar = new FormSliderBar<float>
{
Caption = "Slider",
Current = new BindableFloat
{
MinValue = 0,
MaxValue = 10,
Value = 5,
Precision = 0.1f,
},
}),
new SettingsItemV2(new FormSliderBar<float>
{
Caption = "Slider",
Current = new BindableFloat
{
MinValue = 0,
MaxValue = 10,
Value = 5,
Precision = 0.1f,
Disabled = true,
},
TransferValueOnCommit = true,
}),
new SettingsItemV2(new FormSliderBar<float>
{
Caption = "Slider without revert button",
Current = new BindableFloat
{
MinValue = 0,
MaxValue = 10,
Value = 5,
Precision = 0.1f,
},
})
{
ShowRevertToDefaultButton = false
},
new SettingsItemV2(classicSliderBar = new FormSliderBar<float>
{
Caption = "Slider with classic default",
Current = new BindableFloat
{
MinValue = 0,
MaxValue = 10,
Value = 5,
Precision = 0.1f,
},
})
{
ApplyClassicDefault = () => classicSliderBar.Current.Value = 2,
},
},
},
},
}
},
},
};
}
[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 TestClassicDefault()
{
AddStep("modify irrelevant setting", () => sliderBar.Current.Value = 4);
AddStep("apply classic defaults", () => this.ChildrenOfType<ISettingsItem>().Where(i => i.HasClassicDefault).ForEach(s => s.ApplyClassicDefault()));
AddStep("apply regular defaults", () => this.ChildrenOfType<ISettingsItem>().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<ISettingsItem>().Where(i => i.HasClassicDefault).ForEach(s => s.ApplyClassicDefault()));
AddStep("apply regular defaults", () => this.ChildrenOfType<ISettingsItem>().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));
}
/// <summary>
/// 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).
/// </summary>
[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<SettingsItemV2>().ChildrenOfType<SettingsRevertToDefaultButton>().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;
}
}
}
}
+4
View File
@@ -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,
@@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<bool>
public partial class FormCheckBox : CompositeDrawable, IHasCurrentValue<bool>, IFormControl
{
public Bindable<bool> 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<LocalisableString> FilterTerms => Caption.Yield();
public event Action? ValueChanged;
public bool IsDefault => Current.IsDefault;
public void SetDefault() => Current.SetDefault();
public bool IsDisabled => Current.Disabled;
}
}
@@ -2,6 +2,7 @@
// 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.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
@@ -17,7 +18,7 @@ using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2
{
public partial class FormDropdown<T> : OsuDropdown<T>
public partial class FormDropdown<T> : OsuDropdown<T>, IFormControl
{
/// <summary>
/// Caption describing this slider bar, displayed on top of the controls.
@@ -29,6 +30,12 @@ namespace osu.Game.Graphics.UserInterfaceV2
/// </summary>
public LocalisableString HintText { get; init; }
/// <summary>
/// The maximum height of the dropdown's menu.
/// By default, this is set to 200px high. Set to <see cref="float.PositiveInfinity"/> to remove such limit.
/// </summary>
public float MaxHeight { get; set; } = 200;
private FormDropdownHeader header = null!;
[BackgroundDependencyLoader]
@@ -40,12 +47,40 @@ namespace osu.Game.Graphics.UserInterfaceV2
header.HintText = HintText;
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(_ => ValueChanged?.Invoke());
}
public virtual IEnumerable<LocalisableString> FilterTerms
{
get
{
yield return Caption;
foreach (var item in MenuItems)
yield return item.Text.Value;
}
}
public event Action? ValueChanged;
public bool IsDefault => Current.IsDefault;
public void SetDefault() => Current.SetDefault();
public bool IsDisabled => Current.Disabled;
protected override DropdownHeader CreateHeader() => header = new FormDropdownHeader
{
Dropdown = this,
};
protected override DropdownMenu CreateMenu() => new FormDropdownMenu();
protected override DropdownMenu CreateMenu() => new FormDropdownMenu
{
MaxHeight = MaxHeight,
};
private partial class FormDropdownHeader : DropdownHeader
{
@@ -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<T> : CompositeDrawable, IHasCurrentValue<T>
public partial class FormSliderBar<T> : CompositeDrawable, IHasCurrentValue<T>, IFormControl
where T : struct, INumber<T>, IMinMaxValue<T>
{
public Bindable<T> 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;
@@ -445,7 +451,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
}
else
{
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;
}
}
@@ -487,5 +493,15 @@ namespace osu.Game.Graphics.UserInterfaceV2
return true;
}
}
public IEnumerable<LocalisableString> FilterTerms => new[] { Caption, HintText };
public event Action? ValueChanged;
public bool IsDefault => Current.IsDefault;
public void SetDefault() => Current.SetDefault();
public bool IsDisabled => Current.Disabled;
}
}
@@ -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<string>
public partial class FormTextBox : CompositeDrawable, IHasCurrentValue<string>, IFormControl
{
public Bindable<string> 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 IsDefault => current.IsDefault;
public void SetDefault() => current.SetDefault();
public bool IsDisabled => current.Disabled;
public IEnumerable<LocalisableString> FilterTerms => Caption.Yield();
}
}
@@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// Represents an interface for all form controls.
/// </summary>
public interface IFormControl : IDrawable, IHasFilterTerms
{
/// <summary>
/// Invoked when the value of the control has changed.
/// </summary>
event Action ValueChanged;
/// <summary>
/// Whether the value of this control is in a default state.
/// </summary>
bool IsDefault { get; }
/// <summary>
/// If enabled, resets the control to its default state.
/// </summary>
void SetDefault();
/// <summary>
/// Whether the control is currently disabled.
/// </summary>
bool IsDisabled { get; }
}
}
@@ -0,0 +1,178 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
/// <summary>
/// Whether a revert button should be displayed when the control is modified away from default state.
/// </summary>
public bool ShowRevertToDefaultButton { get; init; } = true;
/// <summary>
/// A note to display underneath the setting.
/// </summary>
public readonly Bindable<SettingsNote.Data?> Note = new Bindable<SettingsNote.Data?>();
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.IsDefault;
controlEnabled.Value = !control.IsDisabled;
controlDefault.BindValueChanged(_ => updateDefaultState());
controlEnabled.BindValueChanged(_ => updateDefaultState(), true);
FinishTransforms(true);
}
private void updateDefaultState()
{
bool showRevertButton = !controlDefault.Value && controlEnabled.Value && ShowRevertToDefaultButton;
if (showRevertButton)
revertButton.Show();
else
revertButton.Hide();
}
protected override void Update()
{
base.Update();
controlDefault.Value = control.IsDefault;
controlEnabled.Value = !control.IsDisabled;
}
#region ISettingsItem
public bool HasClassicDefault => ApplyClassicDefault != null;
/// <summary>
/// If set, this setting is considered as having a "classic" default value,
/// and this is the function for overwriting the control with that value.
/// </summary>
public Action? ApplyClassicDefault { get; set; }
void ISettingsItem.ApplyClassicDefault() => ApplyClassicDefault?.Invoke();
public void ApplyDefault()
{
if (!control.IsDisabled)
control.SetDefault();
}
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<string> Keywords { get; init; } = Enumerable.Empty<string>();
public IEnumerable<LocalisableString> FilterTerms
{
get
{
var filterTerms = new List<LocalisableString>(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<bool> IConditionalFilterable.CanBeShown => CanBeShown;
#endregion
}
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<Data?> Current = new Bindable<Data?>();
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()
{
// Explicitly use ClearTransforms to clear any existing auto-size transform before modifying size / flag.
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,
}
}
}
@@ -0,0 +1,99 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
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);
}
public override LocalisableString TooltipText => CommonStrings.RevertToDefault;
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, 200, Easing.OutElasticQuarter);
}
public override void Hide()
{
this.MoveToX(0, 120, Easing.OutExpo).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);
}
}
}
+3
View File
@@ -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;
+1 -1
View File
@@ -36,7 +36,7 @@
</PackageReference>
<PackageReference Include="Realm" Version="20.1.0" />
<PackageReference Include="ppy.osu.Framework" Version="2025.1209.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.1215.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.1218.0" />
<PackageReference Include="Sentry" Version="5.1.1" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
<PackageReference Include="SharpCompress" Version="0.39.0" />