diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index 5270ac0dc9..6b308f766a 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -8,17 +8,25 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Screens.Play.HUD; using OpenTK; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using System.Linq; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; namespace osu.Game.Tests.Visual { [Description("mod select and icon display")] internal class TestCaseMods : OsuTestCase { - private ModSelectOverlay modSelect; - private ModDisplay modDisplay; + private const string unranked_suffix = " (Unranked)"; private RulesetStore rulesets; - + private ModDisplay modDisplay; + private TestModSelectOverlay modSelect; [BackgroundDependencyLoader] private void load(RulesetStore rulesets) @@ -30,7 +38,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - Add(modSelect = new ModSelectOverlay + Add(modSelect = new TestModSelectOverlay { RelativeSizeAxes = Axes.X, Origin = Anchor.BottomCentre, @@ -48,9 +56,156 @@ namespace osu.Game.Tests.Visual modDisplay.Current.BindTo(modSelect.SelectedMods); AddStep("Toggle", modSelect.ToggleVisibility); + AddStep("Hide", modSelect.Hide); + AddStep("Show", modSelect.Show); - foreach (var ruleset in rulesets.AvailableRulesets) - AddStep(ruleset.CreateInstance().Description, () => modSelect.Ruleset.Value = ruleset); + foreach (var rulesetInfo in rulesets.AvailableRulesets) + { + Ruleset ruleset = rulesetInfo.CreateInstance(); + AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo); + + switch (ruleset) { + case OsuRuleset or: + testOsuMods(or); + break; + } + } + } + + private void testOsuMods(OsuRuleset ruleset) + { + var easierMods = ruleset.GetModsFor(ModType.DifficultyReduction); + var harderMods = ruleset.GetModsFor(ModType.DifficultyIncrease); + var assistMods = ruleset.GetModsFor(ModType.Special); + + var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); + var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); + var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); + var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); + + testSingleMod(noFailMod); + testMultiMod(doubleTimeMod); + testIncompatibleMods(noFailMod, autoPilotMod); + testDeselectAll(easierMods.Where(m => !(m is MultiMod))); + testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); + testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); + testMultiplierTextUnranked(autoPilotMod); + } + + private void testSingleMod(Mod mod) + { + selectNext(mod); + checkSelected(mod); + + selectPrevious(mod); + checkNotSelected(mod); + + selectNext(mod); + selectNext(mod); + checkNotSelected(mod); + + selectPrevious(mod); + selectPrevious(mod); + checkNotSelected(mod); + } + + private void testMultiMod(MultiMod multiMod) + { + foreach (var mod in multiMod.Mods) + { + selectNext(mod); + checkSelected(mod); + } + + for (int index = multiMod.Mods.Length - 1; index >= 0; index--) + selectPrevious(multiMod.Mods[index]); + + foreach (var mod in multiMod.Mods) + checkNotSelected(mod); + } + + private void testIncompatibleMods(Mod modA, Mod modB) + { + selectNext(modA); + checkSelected(modA); + checkNotSelected(modB); + + selectNext(modB); + checkSelected(modB); + checkNotSelected(modA); + + selectPrevious(modB); + checkNotSelected(modA); + checkNotSelected(modB); + } + + private void testDeselectAll(IEnumerable mods) + { + foreach (var mod in mods) + selectNext(mod); + + AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any()); + AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke); + AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); + } + + private void testMultiplierTextColour(Mod mod, Color4 colour) + { + checkLabelColor(Color4.White); + selectNext(mod); + AddWaitStep(1, "wait for changing colour"); + checkLabelColor(colour); + selectPrevious(mod); + AddWaitStep(1, "wait for changing colour"); + checkLabelColor(Color4.White); + } + + private void testMultiplierTextUnranked(Mod mod) + { + AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + selectNext(mod); + AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + selectPrevious(mod); + AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + } + + private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext()); + + private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectPrevious()); + + private void checkSelected(Mod mod) + { + AddAssert($"check {mod.Name} is selected", () => + { + var button = modSelect.GetModButton(mod); + return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; + }); + } + + private void checkNotSelected(Mod mod) + { + AddAssert($"check {mod.Name} is not selected", () => + { + var button = modSelect.GetModButton(mod); + return modSelect.SelectedMods.Value.All(m => m.GetType() != mod.GetType()) && button.SelectedMod?.GetType() != mod.GetType(); + }); + } + + private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color); + + private class TestModSelectOverlay : ModSelectOverlay + { + public ModButton GetModButton(Mod mod) + { + var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type); + return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + } + + public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; + public new TriangleButton DeselectAllButton => base.DeselectAllButton; + + public new Color4 LowMultiplierColour => base.LowMultiplierColour; + public new Color4 HighMultiplierColour => base.HighMultiplierColour; } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9ff21dfdd4..e3be23ebc6 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -17,22 +17,21 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Mods { public class ModSelectOverlay : WaveOverlayContainer { - private const int button_duration = 700; - private const int ranked_multiplier_duration = 700; private const float content_width = 0.8f; - private Color4 lowMultiplierColour, highMultiplierColour; + protected Color4 LowMultiplierColour, HighMultiplierColour; - private readonly OsuSpriteText rankedLabel; - private readonly OsuSpriteText multiplierLabel; - private readonly FillFlowContainer rankedMultiplerContainer; + protected readonly TriangleButton DeselectAllButton; + protected readonly OsuSpriteText MultiplierLabel; + private readonly FillFlowContainer footerContainer; - private readonly FillFlowContainer modSectionsContainer; + protected readonly FillFlowContainer ModSectionsContainer; public readonly Bindable> SelectedMods = new Bindable>(); @@ -42,7 +41,7 @@ namespace osu.Game.Overlays.Mods { var instance = newRuleset.CreateInstance(); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.Mods = instance.GetModsFor(section.ModType); refreshSelectedMods(); } @@ -50,8 +49,8 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets) { - lowMultiplierColour = colours.Red; - highMultiplierColour = colours.Green; + LowMultiplierColour = colours.Red; + HighMultiplierColour = colours.Green; if (osu != null) Ruleset.BindTo(osu.Ruleset); @@ -66,14 +65,14 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - rankedMultiplerContainer.MoveToX(rankedMultiplerContainer.DrawSize.X, APPEAR_DURATION, Easing.InSine); - rankedMultiplerContainer.FadeOut(APPEAR_DURATION, Easing.InSine); + footerContainer.MoveToX(footerContainer.DrawSize.X, DISAPPEAR_DURATION, Easing.InSine); + footerContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) { - section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), APPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.MoveToX(100f, APPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.FadeOut(APPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.MoveToX(100f, DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); } } @@ -81,27 +80,28 @@ namespace osu.Game.Overlays.Mods { base.PopIn(); - rankedMultiplerContainer.MoveToX(0, ranked_multiplier_duration, Easing.OutQuint); - rankedMultiplerContainer.FadeIn(ranked_multiplier_duration, Easing.OutQuint); + footerContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); + footerContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) { - section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), button_duration, Easing.OutQuint); - section.ButtonsContainer.MoveToX(0, button_duration, Easing.OutQuint); - section.ButtonsContainer.FadeIn(button_duration, Easing.OutQuint); + section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); } } public void DeselectAll() { - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.DeselectAll(); + refreshSelectedMods(); } public void DeselectTypes(Type[] modTypes) { if (modTypes.Length == 0) return; - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.DeselectTypes(modTypes); } @@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Mods private void refreshSelectedMods() { - SelectedMods.Value = modSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); double multiplier = 1.0; bool ranked = true; @@ -129,16 +129,16 @@ namespace osu.Game.Overlays.Mods // 1.05x // 1.20x - multiplierLabel.Text = $"{multiplier:N2}x"; - string rankedString = ranked ? "Ranked" : "Unranked"; - rankedLabel.Text = $@"{rankedString}, Score Multiplier: "; + MultiplierLabel.Text = $"{multiplier:N2}x"; + if (!ranked) + MultiplierLabel.Text += " (Unranked)"; if (multiplier > 1.0) - multiplierLabel.FadeColour(highMultiplierColour, 200); + MultiplierLabel.FadeColour(HighMultiplierColour, 200); else if (multiplier < 1.0) - multiplierLabel.FadeColour(lowMultiplierColour, 200); + MultiplierLabel.FadeColour(LowMultiplierColour, 200); else - multiplierLabel.FadeColour(Color4.White, 200); + MultiplierLabel.FadeColour(Color4.White, 200); } public ModSelectOverlay() @@ -232,7 +232,7 @@ namespace osu.Game.Overlays.Mods }, new OsuSpriteText { - Text = @"Others are just for fun", + Text = @"Others are just for fun.", TextSize = 18, Shadow = true, }, @@ -241,7 +241,7 @@ namespace osu.Game.Overlays.Mods }, }, // Body - modSectionsContainer = new FillFlowContainer + ModSectionsContainer = new FillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -289,7 +289,7 @@ namespace osu.Game.Overlays.Mods Colour = new Color4(172, 20, 116, 255), Alpha = 0.5f, }, - rankedMultiplerContainer = new FillFlowContainer + footerContainer = new FillFlowContainer { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -299,26 +299,42 @@ namespace osu.Game.Overlays.Mods Direction = FillDirection.Horizontal, Padding = new MarginPadding { - Top = 20, - Bottom = 20, + Vertical = 15 }, Children = new Drawable[] { - rankedLabel = new OsuSpriteText + DeselectAllButton = new TriangleButton { - Text = @"Ranked, Score Multiplier: ", + Width = 180, + Text = "Deselect All", + Action = DeselectAll, + Margin = new MarginPadding + { + Right = 20 + } + }, + new OsuSpriteText + { + Text = @"Score Multiplier: ", TextSize = 30, Shadow = true, + Margin = new MarginPadding + { + Top = 5 + } }, - multiplierLabel = new OsuSpriteText + MultiplierLabel = new OsuSpriteText { Font = @"Exo2.0-Bold", - Text = @"1.00x", TextSize = 30, Shadow = true, - }, - }, - }, + Margin = new MarginPadding + { + Top = 5 + } + } + } + } }, }, },