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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user