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.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -9,19 +10,26 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
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.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneModPresetColumn : OsuTestScene
public class TestSceneModPresetColumn : OsuManualInputManagerTestScene
{
protected override bool UseFreshStoragePerRun => true;
private Container<Drawable> content = null!;
protected override Container<Drawable> Content => content;
private RulesetStore rulesets = null!;
[Cached]
@ -32,6 +40,12 @@ private void load()
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(Realm);
base.Content.Add(content = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
});
}
[SetUpSteps]
@ -57,15 +71,10 @@ public void SetUpSteps()
public void TestBasicOperation()
{
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
AddStep("create content", () => Child = new Container
AddStep("create content", () => Child = new ModPresetColumn
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
Child = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
@ -112,15 +121,10 @@ public void TestBasicOperation()
public void TestSoftDeleteSupport()
{
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
AddStep("create content", () => Child = new Container
AddStep("create content", () => Child = new ModPresetColumn
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
Child = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
@ -146,6 +150,61 @@ public void TestSoftDeleteSupport()
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[]
{
new ModPreset

View File

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

View File

@ -89,6 +89,16 @@ public static class CommonStrings
/// </summary>
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}";
}
}
}

View File

@ -29,6 +29,11 @@ public static class ModSelectOverlayStrings
/// </summary>
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}";
}
}

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 @@ private void load(OsuColour colours)
{
AccentColour = colours.Orange1;
HeaderText = ModSelectOverlayStrings.PersonalPresets;
AddPresetButton addPresetButton;
ItemsFlow.Add(addPresetButton = new AddPresetButton());
ItemsFlow.SetLayoutPosition(addPresetButton, float.PositiveInfinity);
}
protected override void LoadComplete()
@ -64,7 +68,7 @@ private void asyncLoadPanels(IReadOnlyList<ModPreset> presets)
if (!presets.Any())
{
ItemsFlow.Clear();
ItemsFlow.RemoveAll(panel => panel is ModPresetPanel);
return;
}
@ -77,7 +81,8 @@ private void asyncLoadPanels(IReadOnlyList<ModPreset> presets)
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
{
ItemsFlow.ChildrenEnumerable = loaded;
ItemsFlow.RemoveAll(panel => panel is ModPresetPanel);
ItemsFlow.AddRange(loaded);
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
loadTask.ContinueWith(_ =>
{

View File

@ -43,8 +43,7 @@ public LocalisableString Description
}
public const float CORNER_RADIUS = 7;
protected const float HEIGHT = 42;
public const float HEIGHT = 42;
protected virtual float IdleSwitchWidth => 14;
protected virtual float ExpandedSwitchWidth => 30;