// Copyright (c) ppy Pty Ltd . 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; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneModSettings : OsuManualInputManagerTestScene { private TestModSelectOverlay modSelect; private readonly Mod testCustomisableMod = new TestModCustomisable1(); private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2(); [SetUp] public void SetUp() => Schedule(() => { SelectedMods.Value = Array.Empty(); Ruleset.Value = new TestRulesetInfo(); }); [Test] public void TestButtonShowsOnCustomisableMod() { createModSelect(); openModSelect(); AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded); AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.State.Value == Visibility.Hidden); } [Test] public void TestButtonShowsOnModAlreadyAdded() { AddStep("set active mods", () => SelectedMods.Value = new List { testCustomisableMod }); createModSelect(); AddAssert("mods still active", () => SelectedMods.Value.Count == 1); openModSelect(); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); } [Test] public void TestCustomisationMenuVisibility() { createModSelect(); openModSelect(); AddAssert("Customisation closed", () => modSelect.ModSettingsContainer.State.Value == Visibility.Hidden); AddStep("select mod", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.State.Value == Visibility.Visible); AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); AddAssert("Customisation closed", () => modSelect.ModSettingsContainer.State.Value == Visibility.Hidden); } [Test] public void TestModSettingsUnboundWhenCopied() { OsuModDoubleTime original = null; OsuModDoubleTime copy = null; AddStep("create mods", () => { original = new OsuModDoubleTime(); copy = (OsuModDoubleTime)original.DeepClone(); }); AddStep("change property", () => original.SpeedChange.Value = 2); AddAssert("original has new value", () => Precision.AlmostEquals(2.0, original.SpeedChange.Value)); AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, copy.SpeedChange.Value)); } [Test] public void TestMultiModSettingsUnboundWhenCopied() { MultiMod original = null; MultiMod copy = null; AddStep("create mods", () => { original = new MultiMod(new OsuModDoubleTime()); copy = (MultiMod)original.DeepClone(); }); AddStep("change property", () => ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value = 2); AddAssert("original has new value", () => Precision.AlmostEquals(2.0, ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value)); AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value)); } [Test] public void TestCustomisationMenuNoClickthrough() { createModSelect(); openModSelect(); AddStep("change mod settings menu width to full screen", () => modSelect.SetModSettingsWidth(1.0f)); AddStep("select cm2", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.State.Value == Visibility.Visible); AddStep("hover over mod behind settings menu", () => InputManager.MoveMouseTo(modSelect.GetModButton(testCustomisableMod))); AddAssert("Mod is not considered hovered over", () => !modSelect.GetModButton(testCustomisableMod).IsHovered); AddStep("left click mod", () => InputManager.Click(MouseButton.Left)); AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); AddStep("right click mod", () => InputManager.Click(MouseButton.Right)); AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); } private void createModSelect() { AddStep("create mod select", () => { Child = modSelect = new TestModSelectOverlay { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, SelectedMods = { BindTarget = SelectedMods } }; }); } private void openModSelect() { AddStep("open", () => modSelect.Show()); AddUntilStep("wait for ready", () => modSelect.State.Value == Visibility.Visible && modSelect.ButtonsLoaded); } private class TestModSelectOverlay : LocalPlayerModSelectOverlay { public new VisibilityContainer ModSettingsContainer => base.ModSettingsContainer; public new TriangleButton CustomiseButton => base.CustomiseButton; public bool ButtonsLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); public ModButton GetModButton(Mod mod) { return ModSectionsContainer.ChildrenOfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } public void SelectMod(Mod mod) => GetModButton(mod).SelectNext(1); public void SetModSettingsWidth(float newWidth) => ModSettingsContainer.Parent.Width = newWidth; } public class TestRulesetInfo : RulesetInfo { public override Ruleset CreateInstance() => new TestCustomisableModRuleset(); public TestRulesetInfo() { Available = true; } public class TestCustomisableModRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) { if (type == ModType.Conversion) { return new Mod[] { new TestModCustomisable1(), new TestModCustomisable2() }; } return Array.Empty(); } public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); public override string Description { get; } = "test"; public override string ShortName { get; } = "tst"; } } private class TestModCustomisable1 : TestModCustomisable { public override string Name => "Customisable Mod 1"; public override string Acronym => "CM1"; } private class TestModCustomisable2 : TestModCustomisable { public override string Name => "Customisable Mod 2"; public override string Acronym => "CM2"; public override bool RequiresConfiguration => true; } private abstract class TestModCustomisable : Mod, IApplicableMod { public override double ScoreMultiplier => 1.0; public override string Description => "This is a customisable test mod."; public override ModType Type => ModType.Conversion; [SettingSource("Sample float", "Change something for a mod")] public BindableFloat SliderBindable { get; } = new BindableFloat { MinValue = 0, MaxValue = 10, Default = 5, Value = 7 }; [SettingSource("Sample bool", "Clicking this changes a setting")] public BindableBool TickBindable { get; } = new BindableBool(); } } }