1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 20:13:22 +08:00

Merge pull request #19558 from bdach/mod-overlay/create-preset

Add flow for creating new mod presets
This commit is contained in:
Dean Herbert 2022-08-04 14:45:57 +09:00 committed by GitHub
commit 55234e8069
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 290 additions and 25 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -9,19 +10,26 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
{ {
public class TestSceneModPresetColumn : OsuTestScene public class TestSceneModPresetColumn : OsuManualInputManagerTestScene
{ {
protected override bool UseFreshStoragePerRun => true; protected override bool UseFreshStoragePerRun => true;
private Container<Drawable> content = null!;
protected override Container<Drawable> Content => content;
private RulesetStore rulesets = null!; private RulesetStore rulesets = null!;
[Cached] [Cached]
@ -32,6 +40,12 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(Realm); Dependencies.Cache(Realm);
base.Content.Add(content = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
});
} }
[SetUpSteps] [SetUpSteps]
@ -57,15 +71,10 @@ namespace osu.Game.Tests.Visual.UserInterface
public void TestBasicOperation() public void TestBasicOperation()
{ {
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0)); AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
AddStep("create content", () => Child = new Container AddStep("create content", () => Child = new ModPresetColumn
{ {
RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre,
Padding = new MarginPadding(30), Origin = Anchor.Centre,
Child = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}); });
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3); AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
@ -112,15 +121,10 @@ namespace osu.Game.Tests.Visual.UserInterface
public void TestSoftDeleteSupport() public void TestSoftDeleteSupport()
{ {
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0)); AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
AddStep("create content", () => Child = new Container AddStep("create content", () => Child = new ModPresetColumn
{ {
RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre,
Padding = new MarginPadding(30), Origin = Anchor.Centre,
Child = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}); });
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3); AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
@ -146,6 +150,61 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3); AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
} }
[Test]
public void TestAddingFlow()
{
ModPresetColumn modPresetColumn = null!;
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
AddStep("create content", () => Child = modPresetColumn = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded);
AddAssert("add preset button disabled", () => !this.ChildrenOfType<AddPresetButton>().Single().Enabled.Value);
AddStep("set mods", () => SelectedMods.Value = new Mod[] { new OsuModDaycore(), new OsuModClassic() });
AddAssert("add preset button enabled", () => this.ChildrenOfType<AddPresetButton>().Single().Enabled.Value);
AddStep("click add preset button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<AddPresetButton>().Single());
InputManager.Click(MouseButton.Left);
});
OsuPopover? popover = null;
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<OsuPopover>().FirstOrDefault()) != null);
AddStep("attempt preset creation", () =>
{
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddWaitStep("wait some", 3);
AddAssert("preset creation did not occur", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
AddUntilStep("popover is unchanged", () => this.ChildrenOfType<OsuPopover>().FirstOrDefault() == popover);
AddStep("fill preset name", () => popover.ChildrenOfType<LabelledTextBox>().First().Current.Value = "new preset");
AddStep("attempt preset creation", () =>
{
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("popover closed", () => !this.ChildrenOfType<OsuPopover>().Any());
AddUntilStep("preset creation occurred", () => this.ChildrenOfType<ModPresetPanel>().Count() == 4);
AddStep("click add preset button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<AddPresetButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<OsuPopover>().FirstOrDefault()) != null);
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
AddUntilStep("popover closed", () => !this.ChildrenOfType<OsuPopover>().Any());
}
private ICollection<ModPreset> createTestPresets() => new[] private ICollection<ModPreset> createTestPresets() => new[]
{ {
new ModPreset new ModPreset

View File

@ -49,15 +49,15 @@ namespace osu.Game.Graphics.UserInterface
Active.BindDisabledChanged(disabled => Action = disabled ? null : Active.Toggle, true); Active.BindDisabledChanged(disabled => Action = disabled ? null : Active.Toggle, true);
Active.BindValueChanged(_ => Active.BindValueChanged(_ =>
{ {
updateActiveState(); UpdateActiveState();
playSample(); playSample();
}); });
updateActiveState(); UpdateActiveState();
base.LoadComplete(); base.LoadComplete();
} }
private void updateActiveState() protected virtual void UpdateActiveState()
{ {
DarkerColour = Active.Value ? ColourProvider.Highlight1 : ColourProvider.Background3; DarkerColour = Active.Value ? ColourProvider.Highlight1 : ColourProvider.Background3;
LighterColour = Active.Value ? ColourProvider.Colour0 : ColourProvider.Background1; LighterColour = Active.Value ? ColourProvider.Colour0 : ColourProvider.Background1;

View File

@ -89,6 +89,16 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections"); public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections");
/// <summary>
/// "Name"
/// </summary>
public static LocalisableString Name => new TranslatableString(getKey(@"name"), @"Name");
/// <summary>
/// "Description"
/// </summary>
public static LocalisableString Description => new TranslatableString(getKey(@"description"), @"Description");
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -29,6 +29,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets"); public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets");
/// <summary>
/// "Add preset"
/// </summary>
public static LocalisableString AddPreset => new TranslatableString(getKey(@"add_preset"), @"Add preset");
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -0,0 +1,67 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
using osuTK;
namespace osu.Game.Overlays.Mods
{
public class AddPresetButton : ShearedToggleButton, IHasPopover
{
[Resolved]
private OsuColour colours { get; set; } = null!;
[Resolved]
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;
public AddPresetButton()
: base(1)
{
RelativeSizeAxes = Axes.X;
Height = ModSelectPanel.HEIGHT;
// shear will be applied at a higher level in `ModPresetColumn`.
Content.Shear = Vector2.Zero;
Padding = new MarginPadding();
Text = "+";
TextSize = 30;
}
protected override void LoadComplete()
{
base.LoadComplete();
selectedMods.BindValueChanged(mods => Enabled.Value = mods.NewValue.Any(), true);
Enabled.BindValueChanged(enabled =>
{
if (!enabled.NewValue)
Active.Value = false;
});
}
protected override void UpdateActiveState()
{
DarkerColour = Active.Value ? colours.Orange1 : ColourProvider.Background3;
LighterColour = Active.Value ? colours.Orange0 : ColourProvider.Background1;
TextColour = Active.Value ? ColourProvider.Background6 : ColourProvider.Content1;
if (Active.Value)
this.ShowPopover();
else
this.HidePopover();
}
public Popover GetPopover() => new AddPresetPopover(this);
}
}

View File

@ -0,0 +1,120 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osuTK;
namespace osu.Game.Overlays.Mods
{
internal class AddPresetPopover : OsuPopover
{
private readonly AddPresetButton button;
private readonly LabelledTextBox nameTextBox;
private readonly LabelledTextBox descriptionTextBox;
private readonly ShearedButton createButton;
[Resolved]
private Bindable<RulesetInfo> ruleset { get; set; } = null!;
[Resolved]
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;
[Resolved]
private RealmAccess realm { get; set; } = null!;
public AddPresetPopover(AddPresetButton addPresetButton)
{
button = addPresetButton;
Child = new FillFlowContainer
{
Width = 300,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(7),
Children = new Drawable[]
{
nameTextBox = new LabelledTextBox
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Label = CommonStrings.Name,
TabbableContentContainer = this
},
descriptionTextBox = new LabelledTextBox
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Label = CommonStrings.Description,
TabbableContentContainer = this
},
createButton = new ShearedButton
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = ModSelectOverlayStrings.AddPreset,
Action = tryCreatePreset
}
}
};
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours)
{
Body.BorderThickness = 3;
Body.BorderColour = colours.Orange1;
createButton.DarkerColour = colours.Orange1;
createButton.LighterColour = colours.Orange0;
createButton.TextColour = colourProvider.Background6;
}
protected override void LoadComplete()
{
base.LoadComplete();
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox));
}
private void tryCreatePreset()
{
if (string.IsNullOrWhiteSpace(nameTextBox.Current.Value))
{
Body.Shake();
return;
}
realm.Write(r => r.Add(new ModPreset
{
Name = nameTextBox.Current.Value,
Description = descriptionTextBox.Current.Value,
Mods = selectedMods.Value.ToArray(),
Ruleset = r.Find<RulesetInfo>(ruleset.Value.ShortName)
}));
this.HidePopover();
}
protected override void UpdateState(ValueChangedEvent<Visibility> state)
{
base.UpdateState(state);
if (state.NewValue == Visibility.Hidden)
button.Active.Value = false;
}
}
}

View File

@ -31,6 +31,10 @@ namespace osu.Game.Overlays.Mods
{ {
AccentColour = colours.Orange1; AccentColour = colours.Orange1;
HeaderText = ModSelectOverlayStrings.PersonalPresets; HeaderText = ModSelectOverlayStrings.PersonalPresets;
AddPresetButton addPresetButton;
ItemsFlow.Add(addPresetButton = new AddPresetButton());
ItemsFlow.SetLayoutPosition(addPresetButton, float.PositiveInfinity);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -64,7 +68,7 @@ namespace osu.Game.Overlays.Mods
if (!presets.Any()) if (!presets.Any())
{ {
ItemsFlow.Clear(); ItemsFlow.RemoveAll(panel => panel is ModPresetPanel);
return; return;
} }
@ -77,7 +81,8 @@ namespace osu.Game.Overlays.Mods
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
{ {
ItemsFlow.ChildrenEnumerable = loaded; ItemsFlow.RemoveAll(panel => panel is ModPresetPanel);
ItemsFlow.AddRange(loaded);
}, (cancellationTokenSource = new CancellationTokenSource()).Token); }, (cancellationTokenSource = new CancellationTokenSource()).Token);
loadTask.ContinueWith(_ => loadTask.ContinueWith(_ =>
{ {

View File

@ -43,8 +43,7 @@ namespace osu.Game.Overlays.Mods
} }
public const float CORNER_RADIUS = 7; public const float CORNER_RADIUS = 7;
public const float HEIGHT = 42;
protected const float HEIGHT = 42;
protected virtual float IdleSwitchWidth => 14; protected virtual float IdleSwitchWidth => 14;
protected virtual float ExpandedSwitchWidth => 30; protected virtual float ExpandedSwitchWidth => 30;