diff --git a/osu.Game.Tests/Mods/ModSettingsTest.cs b/osu.Game.Tests/Mods/ModSettingsTest.cs new file mode 100644 index 0000000000..b9ea1f2567 --- /dev/null +++ b/osu.Game.Tests/Mods/ModSettingsTest.cs @@ -0,0 +1,36 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Mods +{ + public class ModSettingsTest + { + [Test] + public void TestModSettingsUnboundWhenCopied() + { + var original = new OsuModDoubleTime(); + var copy = (OsuModDoubleTime)original.DeepClone(); + + original.SpeedChange.Value = 2; + + Assert.That(original.SpeedChange.Value, Is.EqualTo(2.0)); + Assert.That(copy.SpeedChange.Value, Is.EqualTo(1.5)); + } + + [Test] + public void TestMultiModSettingsUnboundWhenCopied() + { + var original = new MultiMod(new OsuModDoubleTime()); + var copy = (MultiMod)original.DeepClone(); + + ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value = 2; + + Assert.That(((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value, Is.EqualTo(2.0)); + Assert.That(((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value, Is.EqualTo(1.5)); + } + } +} diff --git a/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs new file mode 100644 index 0000000000..3992d9abe6 --- /dev/null +++ b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs @@ -0,0 +1,80 @@ +// 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 osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Mods +{ + public class TestCustomisableModRuleset : Ruleset + { + public static RulesetInfo CreateTestRulesetInfo() => new TestCustomisableModRuleset().RulesetInfo; + + 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(IWorkingBeatmap beatmap) => throw new NotImplementedException(); + + public override string Description { get; } = "test"; + public override string ShortName { get; } = "tst"; + + public class TestModCustomisable1 : TestModCustomisable + { + public override string Name => "Customisable Mod 1"; + + public override string Acronym => "CM1"; + } + + public class TestModCustomisable2 : TestModCustomisable + { + public override string Name => "Customisable Mod 2"; + + public override string Acronym => "CM2"; + + public override bool RequiresConfiguration => true; + } + + public 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(); + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 8df32c500e..81763564fa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -24,7 +24,7 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Tests.Visual.UserInterface; +using osu.Game.Tests.Mods; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay { new Drawable[] { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Recorder = recorder = new TestReplayRecorder(new Score { @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, new Drawable[] { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { ReplayInputHandler = new TestFramedReplayInputHandler(replay) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4ec46036f6..f8748922cf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -27,8 +27,8 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Mods; using osu.Game.Tests.Visual.Spectator; -using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay { new Drawable[] { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Recorder = recorder = new TestReplayRecorder { @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, new Drawable[] { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Clock = new FramedClock(manualClock), ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs deleted file mode 100644 index 26a0301d8a..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using osu.Framework.Graphics.Containers; -using osu.Game.Screens.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestSceneFreeModSelectOverlay : MultiplayerTestScene - { - [SetUp] - public new void Setup() => Schedule(() => - { - Child = new FreeModSelectOverlay - { - State = { Value = Visibility.Visible } - }; - }); - } -} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs deleted file mode 100644 index fdc21d80ff..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneModButton : OsuTestScene - { - public TestSceneModButton() - { - Children = new Drawable[] - { - new ModButton(new MultiMod(new TestMod1(), new TestMod2(), new TestMod3(), new TestMod4())) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - } - }; - } - - private class TestMod1 : TestMod - { - public override string Name => "Test mod 1"; - - public override string Acronym => "M1"; - } - - private class TestMod2 : TestMod - { - public override string Name => "Test mod 2"; - - public override string Acronym => "M2"; - - public override IconUsage? Icon => FontAwesome.Solid.Exclamation; - } - - private class TestMod3 : TestMod - { - public override string Name => "Test mod 3"; - - public override string Acronym => "M3"; - - public override IconUsage? Icon => FontAwesome.Solid.ArrowRight; - } - - private class TestMod4 : TestMod - { - public override string Name => "Test mod 4"; - - public override string Acronym => "M4"; - } - - private abstract class TestMod : Mod, IApplicableMod - { - public override double ScoreMultiplier => 1.0; - - public override string Description => "This is a test mod."; - } - } -} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs deleted file mode 100644 index b429619044..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ /dev/null @@ -1,472 +0,0 @@ -// 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.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Mods; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Play.HUD; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.UserInterface -{ - [Description("mod select and icon display")] - public class TestSceneModSelectOverlay : OsuTestScene - { - private RulesetStore rulesets; - private ModDisplay modDisplay; - private TestModSelectOverlay modSelect; - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - - [SetUp] - public void SetUp() => Schedule(() => - { - SelectedMods.Value = Array.Empty(); - createDisplay(() => new TestModSelectOverlay()); - }); - - [SetUpSteps] - public void SetUpSteps() - { - AddStep("show", () => modSelect.Show()); - } - - /// - /// Ensure that two mod overlays are not cross polluting via central settings instances. - /// - [Test] - public void TestSettingsNotCrossPolluting() - { - Bindable> selectedMods2 = null; - - AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); - - AddStep("set setting", () => modSelect.ChildrenOfType>().First().Current.Value = 8); - - AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); - - AddStep("create second bindable", () => selectedMods2 = new Bindable>(new Mod[] { new OsuModDifficultyAdjust() })); - - AddStep("create second overlay", () => - { - Add(modSelect = new TestModSelectOverlay().With(d => - { - d.Origin = Anchor.TopCentre; - d.Anchor = Anchor.TopCentre; - d.SelectedMods.BindTarget = selectedMods2; - })); - }); - - AddStep("show", () => modSelect.Show()); - - AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); - AddAssert("ensure second is default", () => selectedMods2.Value.OfType().Single().CircleSize.Value == null); - } - - [Test] - public void TestSettingsResetOnDeselection() - { - var osuModDoubleTime = new OsuModDoubleTime { SpeedChange = { Value = 1.2 } }; - - changeRuleset(0); - - AddStep("set dt mod with custom rate", () => { SelectedMods.Value = new[] { osuModDoubleTime }; }); - - AddAssert("selected mod matches", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.Value == 1.2); - - AddStep("deselect", () => modSelect.DeselectAllButton.TriggerClick()); - AddAssert("selected mods empty", () => SelectedMods.Value.Count == 0); - - AddStep("reselect", () => modSelect.GetModButton(osuModDoubleTime).TriggerClick()); - AddAssert("selected mod has default value", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.IsDefault == true); - } - - [Test] - public void TestAnimationFlushOnClose() - { - changeRuleset(0); - - AddStep("Select all fun mods", () => - { - modSelect.ModSectionsContainer - .Single(c => c.ModType == ModType.DifficultyIncrease) - .SelectAll(); - }); - - AddUntilStep("many mods selected", () => modDisplay.Current.Value.Count >= 5); - - AddStep("trigger deselect and close overlay", () => - { - modSelect.ModSectionsContainer - .Single(c => c.ModType == ModType.DifficultyIncrease) - .DeselectAll(); - - modSelect.Hide(); - }); - - AddAssert("all mods deselected", () => modDisplay.Current.Value.Count == 0); - } - - [Test] - public void TestOsuMods() - { - changeRuleset(0); - - var osu = new OsuRuleset(); - - var easierMods = osu.GetModsFor(ModType.DifficultyReduction); - var harderMods = osu.GetModsFor(ModType.DifficultyIncrease); - - var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - - var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - - var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); - var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); - - testSingleMod(noFailMod); - testMultiMod(doubleTimeMod); - testIncompatibleMods(easy, hardRock); - testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - } - - [Test] - public void TestManiaMods() - { - changeRuleset(3); - - var mania = new ManiaRuleset(); - - testModsWithSameBaseType( - mania.CreateMod(), - mania.CreateMod()); - } - - [Test] - public void TestRulesetChanges() - { - changeRuleset(0); - - var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - - AddStep("set mods externally", () => { SelectedMods.Value = new[] { noFailMod }; }); - - changeRuleset(0); - - AddAssert("ensure mods still selected", () => modDisplay.Current.Value.SingleOrDefault(m => m is OsuModNoFail) != null); - - changeRuleset(3); - - AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0); - - changeRuleset(0); - - AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0); - } - - [Test] - public void TestExternallySetCustomizedMod() - { - changeRuleset(0); - - AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }); - - AddAssert("ensure button is selected and customized accordingly", () => - { - var button = modSelect.GetModButton(SelectedMods.Value.Single()); - return ((OsuModDoubleTime)button.SelectedMod).SpeedChange.Value == 1.01; - }); - } - - [Test] - public void TestSettingsAreRetainedOnReload() - { - changeRuleset(0); - - AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }); - - AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01); - - AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay())); - - AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01); - } - - [Test] - public void TestExternallySetModIsReplacedByOverlayInstance() - { - Mod external = new OsuModDoubleTime(); - Mod overlayButtonMod = null; - - changeRuleset(0); - - AddStep("set mod externally", () => { SelectedMods.Value = new[] { external }; }); - - AddAssert("ensure button is selected", () => - { - var button = modSelect.GetModButton(SelectedMods.Value.Single()); - overlayButtonMod = button.SelectedMod; - return overlayButtonMod.GetType() == external.GetType(); - }); - - // Right now, when an external change occurs, the ModSelectOverlay will replace the global instance with its own - AddAssert("mod instance doesn't match", () => external != overlayButtonMod); - - AddAssert("one mod present in global selected", () => SelectedMods.Value.Count == 1); - AddAssert("globally selected matches button's mod instance", () => SelectedMods.Value.Contains(overlayButtonMod)); - AddAssert("globally selected doesn't contain original external change", () => !SelectedMods.Value.Contains(external)); - } - - [Test] - public void TestNonStacked() - { - changeRuleset(0); - - AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay())); - - AddStep("show", () => modSelect.Show()); - - AddAssert("ensure all buttons are spread out", () => modSelect.ChildrenOfType().All(m => m.Mods.Length <= 1)); - } - - [Test] - public void TestChangeIsValidChangesButtonVisibility() - { - changeRuleset(0); - - AddAssert("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); - - AddStep("make double time invalid", () => modSelect.IsValidMod = m => !(m is OsuModDoubleTime)); - AddUntilStep("double time not visible", () => modSelect.ChildrenOfType().All(b => !b.Mods.Any(m => m is OsuModDoubleTime))); - AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); - - AddStep("make double time valid again", () => modSelect.IsValidMod = m => true); - AddUntilStep("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); - AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); - } - - [Test] - public void TestChangeIsValidPreservesSelection() - { - changeRuleset(0); - - AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); - AddAssert("DT + HD selected", () => modSelect.ChildrenOfType().Count(b => b.Selected) == 2); - - AddStep("make NF invalid", () => modSelect.IsValidMod = m => !(m is ModNoFail)); - AddAssert("DT + HD still selected", () => modSelect.ChildrenOfType().Count(b => b.Selected) == 2); - } - - [Test] - public void TestUnimplementedModIsUnselectable() - { - var testRuleset = new TestUnimplementedModOsuRuleset(); - changeTestRuleset(testRuleset.RulesetInfo); - - var conversionMods = testRuleset.GetModsFor(ModType.Conversion); - - var unimplementedMod = conversionMods.FirstOrDefault(m => m is TestUnimplementedMod); - - testUnimplementedMod(unimplementedMod); - } - - 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 testUnimplementedMod(Mod mod) - { - selectNext(mod); - 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 testModsWithSameBaseType(Mod modA, Mod modB) - { - selectNext(modA); - checkSelected(modA); - selectNext(modB); - checkSelected(modB); - - // Backwards - selectPrevious(modA); - checkSelected(modA); - } - - private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); - - private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(-1)); - - private void checkSelected(Mod mod) - { - AddAssert($"check {mod.Name} is selected", () => - { - var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.SingleOrDefault(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; - }); - } - - private void changeRuleset(int? onlineId) - { - AddStep($"change ruleset to {(onlineId?.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.OnlineID == onlineId); }); - waitForLoad(); - } - - private void changeTestRuleset(RulesetInfo rulesetInfo) - { - AddStep($"change ruleset to {rulesetInfo.Name}", () => { Ruleset.Value = rulesetInfo; }); - waitForLoad(); - } - - private void waitForLoad() => - AddUntilStep("wait for icons to load", () => modSelect.AllLoaded); - - 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 createDisplay(Func createOverlayFunc) - { - Children = new Drawable[] - { - modSelect = createOverlayFunc().With(d => - { - d.Origin = Anchor.BottomCentre; - d.Anchor = Anchor.BottomCentre; - d.SelectedMods.BindTarget = SelectedMods; - }), - modDisplay = new ModDisplay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Position = new Vector2(-5, 25), - Current = { BindTarget = modSelect.SelectedMods } - } - }; - } - - private class TestModSelectOverlay : UserModSelectOverlay - { - public new Bindable> SelectedMods => base.SelectedMods; - - public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); - - public new FillFlowContainer ModSectionsContainer => - base.ModSectionsContainer; - - 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 TriangleButton DeselectAllButton => base.DeselectAllButton; - - public new Color4 LowMultiplierColour => base.LowMultiplierColour; - public new Color4 HighMultiplierColour => base.HighMultiplierColour; - } - - private class TestNonStackedModSelectOverlay : TestModSelectOverlay - { - protected override bool Stacked => false; - } - - private class TestUnimplementedMod : Mod - { - public override string Name => "Unimplemented mod"; - public override string Acronym => "UM"; - public override string Description => "A mod that is not implemented."; - public override double ScoreMultiplier => 1; - public override ModType Type => ModType.Conversion; - } - - private class TestUnimplementedModOsuRuleset : OsuRuleset - { - public override string ShortName => "unimplemented"; - - public override IEnumerable GetModsFor(ModType type) - { - if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }); - - return base.GetModsFor(type); - } - } - } -} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs deleted file mode 100644 index 9a3083e8db..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ /dev/null @@ -1,238 +0,0 @@ -// 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 = CreateTestRulesetInfo(); - }); - - [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.TriggerClick()); - 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 : UserModSelectOverlay - { - 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 static RulesetInfo CreateTestRulesetInfo() => new TestCustomisableModRuleset().RulesetInfo; - - 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(IWorkingBeatmap 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(); - } - } -} diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs deleted file mode 100644 index 6e2cb40596..0000000000 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Game.Rulesets.Mods; -using osu.Game.Utils; -using osuTK; - -namespace osu.Game.Overlays.Mods -{ - public class IncompatibilityDisplayingModButton : ModButton - { - private readonly CompositeDrawable incompatibleIcon; - - [Resolved] - private Bindable> selectedMods { get; set; } - - public IncompatibilityDisplayingModButton(Mod mod) - : base(mod) - { - ButtonContent.Add(incompatibleIcon = new IncompatibleIcon - { - Anchor = Anchor.BottomRight, - Origin = Anchor.Centre, - Position = new Vector2(-13), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateCompatibility), true); - } - - protected override void DisplayMod(Mod mod) - { - base.DisplayMod(mod); - - Scheduler.AddOnce(updateCompatibility); - } - - private void updateCompatibility() - { - var m = SelectedMod ?? Mods.First(); - - bool isIncompatible = false; - - if (selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(m)) - isIncompatible = !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(m)); - - if (isIncompatible) - incompatibleIcon.Show(); - else - incompatibleIcon.Hide(); - } - - public override ITooltip GetCustomTooltip() => new IncompatibilityDisplayingTooltip(); - } -} diff --git a/osu.Game/Overlays/Mods/IncompatibleIcon.cs b/osu.Game/Overlays/Mods/IncompatibleIcon.cs deleted file mode 100644 index df134fe4a4..0000000000 --- a/osu.Game/Overlays/Mods/IncompatibleIcon.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Mods -{ - public class IncompatibleIcon : VisibilityContainer, IHasTooltip - { - private Circle circle; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Size = new Vector2(20); - - State.Value = Visibility.Hidden; - Alpha = 0; - - InternalChildren = new Drawable[] - { - circle = new Circle - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4, - }, - new SpriteIcon - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(0.6f), - Icon = FontAwesome.Solid.Slash, - Colour = Color4.White, - Shadow = true, - } - }; - } - - protected override void PopIn() - { - this.FadeIn(200, Easing.OutQuint); - circle.FlashColour(Color4.Red, 500, Easing.OutQuint); - this.ScaleTo(1.8f).Then().ScaleTo(1, 500, Easing.OutQuint); - } - - protected override void PopOut() - { - this.FadeOut(200, Easing.OutQuint); - this.ScaleTo(0.8f, 200, Easing.In); - } - - public LocalisableString TooltipText => "Incompatible with current selected mods"; - } -} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs deleted file mode 100644 index 979e2c8da3..0000000000 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osuTK; -using osuTK.Graphics; -using osuTK.Input; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; -using System; -using System.Linq; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Input.Events; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Mods -{ - /// - /// Represents a clickable button which can cycle through one of more mods. - /// - public class ModButton : ModButtonEmpty, IHasCustomTooltip - { - private ModIcon foregroundIcon; - private ModIcon backgroundIcon; - private readonly SpriteText text; - private readonly Container iconsContainer; - - /// - /// Fired when the selection changes. - /// - public Action SelectionChanged; - - public LocalisableString TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; - - private const Easing mod_switch_easing = Easing.InOutSine; - private const double mod_switch_duration = 120; - - // A selected index of -1 means not selected. - private int selectedIndex = -1; - - /// - /// Change the selected mod index of this button. - /// - /// The new index. - /// Whether any settings applied to the mod should be reset on selection. - /// Whether the selection changed. - private bool changeSelectedIndex(int newIndex, bool resetSettings = true) - { - if (newIndex == selectedIndex) return false; - - int direction = newIndex < selectedIndex ? -1 : 1; - - bool beforeSelected = Selected; - - Mod previousSelection = SelectedMod ?? Mods[0]; - - if (newIndex >= Mods.Length) - newIndex = -1; - else if (newIndex < -1) - newIndex = Mods.Length - 1; - - if (newIndex >= 0 && !Mods[newIndex].HasImplementation) - return false; - - selectedIndex = newIndex; - - Mod newSelection = SelectedMod ?? Mods[0]; - - if (resetSettings) - newSelection.ResetSettingsToDefaults(); - - Schedule(() => - { - if (beforeSelected != Selected) - { - iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic); - iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic); - } - - if (previousSelection != newSelection) - { - const float rotate_angle = 16; - - foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing); - backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); - - backgroundIcon.Mod = newSelection; - - using (BeginDelayedSequence(mod_switch_duration)) - { - foregroundIcon - .RotateTo(-rotate_angle * direction) - .RotateTo(0f, mod_switch_duration, mod_switch_easing); - - backgroundIcon - .RotateTo(rotate_angle * direction) - .RotateTo(0f, mod_switch_duration, mod_switch_easing); - - Schedule(() => DisplayMod(newSelection)); - } - } - - foregroundIcon.Selected.Value = Selected; - }); - - SelectionChanged?.Invoke(SelectedMod); - - return true; - } - - public bool Selected => selectedIndex != -1; - - private Color4 selectedColour; - - public Color4 SelectedColour - { - get => selectedColour; - set - { - if (value == selectedColour) return; - - selectedColour = value; - if (Selected) foregroundIcon.Colour = value; - } - } - - private Mod mod; - - protected readonly Container ButtonContent; - - public Mod Mod - { - get => mod; - set - { - mod = value; - - if (mod == null) - { - Mods = Array.Empty(); - Alpha = 0; - } - else - { - Mods = (mod as MultiMod)?.Mods ?? new[] { mod }; - Alpha = 1; - } - - createIcons(); - - if (Mods.Length > 0) - { - DisplayMod(Mods[0]); - } - } - } - - public Mod[] Mods { get; private set; } - - public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); - - protected override bool OnMouseDown(MouseDownEvent e) - { - ButtonContent.ScaleTo(0.9f, 800, Easing.Out); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - ButtonContent.ScaleTo(1, 500, Easing.OutElastic); - - // only trigger the event if we are inside the area of the button - if (Contains(e.ScreenSpaceMousePosition)) - { - switch (e.Button) - { - case MouseButton.Right: - SelectNext(-1); - break; - } - } - } - - protected override bool OnClick(ClickEvent e) - { - SelectNext(1); - - return true; - } - - /// - /// Select the next available mod in a specified direction. - /// - /// 1 for forwards, -1 for backwards. - public void SelectNext(int direction) - { - int start = selectedIndex + direction; - // wrap around if we are at an extremity. - if (start >= Mods.Length) - start = -1; - else if (start < -1) - start = Mods.Length - 1; - - for (int i = start; i < Mods.Length && i >= 0; i += direction) - { - if (SelectAt(i)) - return; - } - - Deselect(); - } - - /// - /// Select the mod at the provided index. - /// - /// The index to select. - /// Whether any settings applied to the mod should be reset on selection. - /// Whether the selection changed. - public bool SelectAt(int index, bool resetSettings = true) - { - if (!Mods[index].HasImplementation) return false; - - changeSelectedIndex(index, resetSettings); - return true; - } - - public void Deselect() => changeSelectedIndex(-1); - - protected virtual void DisplayMod(Mod mod) - { - if (backgroundIcon != null) - backgroundIcon.Mod = foregroundIcon.Mod; - foregroundIcon.Mod = mod; - text.Text = mod.Name; - Colour = mod.HasImplementation ? Color4.White : Color4.Gray; - } - - private void createIcons() - { - iconsContainer.Clear(); - - if (Mods.Length > 1) - { - iconsContainer.AddRange(new[] - { - backgroundIcon = new ModIcon(Mods[1], false) - { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Position = new Vector2(1.5f), - }, - foregroundIcon = new ModIcon(Mods[0], false) - { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Position = new Vector2(-1.5f), - }, - }); - } - else - { - iconsContainer.Add(foregroundIcon = new ModIcon(Mod, false) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - }); - } - } - - public ModButton(Mod mod) - { - Children = new Drawable[] - { - new Container - { - Size = new Vector2(77f, 80f), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - ButtonContent = new Container - { - Children = new Drawable[] - { - iconsContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - }, - }, - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - } - }, - text = new OsuSpriteText - { - Y = 75, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Font = OsuFont.GetFont(size: 18) - }, - new HoverSounds() - }; - Mod = mod; - } - - public virtual ITooltip GetCustomTooltip() => new ModButtonTooltip(); - - public Mod TooltipContent => SelectedMod ?? Mods.FirstOrDefault(); - } -} diff --git a/osu.Game/Overlays/Mods/ModButtonEmpty.cs b/osu.Game/Overlays/Mods/ModButtonEmpty.cs deleted file mode 100644 index 03afe5adba..0000000000 --- a/osu.Game/Overlays/Mods/ModButtonEmpty.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osuTK; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Overlays.Mods -{ - /// - /// A mod button used exclusively for providing an empty space the size of a mod button. - /// - public class ModButtonEmpty : Container - { - public ModButtonEmpty() - { - Size = new Vector2(100f); - AlwaysPresent = true; - } - } -} diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs deleted file mode 100644 index 10b3bc7c2b..0000000000 --- a/osu.Game/Overlays/Mods/ModControlSection.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osuTK; - -namespace osu.Game.Overlays.Mods -{ - public class ModControlSection : CompositeDrawable - { - protected FillFlowContainer FlowContent; - - public readonly Mod Mod; - - public ModControlSection(Mod mod, IEnumerable modControls) - { - Mod = mod; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - FlowContent = new FillFlowContainer - { - Margin = new MarginPadding { Top = 30 }, - Spacing = new Vector2(0, 5), - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - ChildrenEnumerable = modControls - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AddRangeInternal(new Drawable[] - { - new OsuSpriteText - { - Text = Mod.Name, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Colour = colours.Yellow, - }, - FlowContent - }); - } - } -} diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs deleted file mode 100644 index a70191a864..0000000000 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osuTK; -using osuTK.Input; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading; -using Humanizer; -using osu.Framework.Input.Events; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Mods -{ - public class ModSection : CompositeDrawable - { - private readonly Drawable header; - - public FillFlowContainer ButtonsContainer { get; } - - protected IReadOnlyList Buttons { get; private set; } = Array.Empty(); - - public Action Action; - - public Key[] ToggleKeys; - - public readonly ModType ModType; - - public IEnumerable SelectedMods => Buttons.Select(b => b.SelectedMod).Where(m => m != null); - - private CancellationTokenSource modsLoadCts; - - protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; - - /// - /// True when all mod icons have completed loading. - /// - public bool ModIconsLoaded { get; private set; } = true; - - public IEnumerable Mods - { - set - { - var modContainers = value.Select(m => - { - if (m == null) - return new ModButtonEmpty(); - - return CreateModButton(m).With(b => - { - b.SelectionChanged = mod => - { - ModButtonStateChanged(mod); - Action?.Invoke(mod); - }; - }); - }).ToArray(); - - modsLoadCts?.Cancel(); - - if (modContainers.Length == 0) - { - ModIconsLoaded = true; - header.Hide(); - Hide(); - return; - } - - ModIconsLoaded = false; - - LoadComponentsAsync(modContainers, c => - { - ModIconsLoaded = true; - ButtonsContainer.ChildrenEnumerable = c; - }, (modsLoadCts = new CancellationTokenSource()).Token); - - Buttons = modContainers.OfType().ToArray(); - - header.FadeIn(200); - this.FadeIn(200); - } - } - - protected virtual void ModButtonStateChanged(Mod mod) - { - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.ControlPressed) return false; - - if (ToggleKeys != null) - { - int index = Array.IndexOf(ToggleKeys, e.Key); - if (index > -1 && index < Buttons.Count) - Buttons[index].SelectNext(e.ShiftPressed ? -1 : 1); - } - - return base.OnKeyDown(e); - } - - private const double initial_multiple_selection_delay = 120; - - private double selectionDelay = initial_multiple_selection_delay; - private double lastSelection; - - private readonly Queue pendingSelectionOperations = new Queue(); - - protected override void Update() - { - base.Update(); - - if (selectionDelay == initial_multiple_selection_delay || Time.Current - lastSelection >= selectionDelay) - { - if (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) - { - dequeuedAction(); - - // each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements). - selectionDelay = Math.Max(30, selectionDelay * 0.8f); - lastSelection = Time.Current; - } - else - { - // reset the selection delay after all animations have been completed. - // this will cause the next action to be immediately performed. - selectionDelay = initial_multiple_selection_delay; - } - } - } - - /// - /// Selects all mods. - /// - public void SelectAll() - { - pendingSelectionOperations.Clear(); - - foreach (var button in Buttons.Where(b => !b.Selected)) - pendingSelectionOperations.Enqueue(() => button.SelectAt(0)); - } - - /// - /// Deselects all mods. - /// - public void DeselectAll() - { - pendingSelectionOperations.Clear(); - DeselectTypes(Buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); - } - - /// - /// Deselect one or more mods in this section. - /// - /// The types of s which should be deselected. - /// Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow. - /// If this deselection is triggered by a user selection, this should contain the newly selected type. This type will never be deselected, even if it matches one provided in . - public void DeselectTypes(IEnumerable modTypes, bool immediate = false, Mod newSelection = null) - { - foreach (var button in Buttons) - { - if (button.SelectedMod == null) continue; - - if (button.SelectedMod == newSelection) - continue; - - foreach (var type in modTypes) - { - if (type.IsInstanceOfType(button.SelectedMod)) - { - if (immediate) - button.Deselect(); - else - pendingSelectionOperations.Enqueue(button.Deselect); - } - } - } - } - - /// - /// Updates all buttons with the given list of selected mods. - /// - /// The new list of selected mods to select. - public void UpdateSelectedButtons(IReadOnlyList newSelectedMods) - { - foreach (var button in Buttons) - updateButtonSelection(button, newSelectedMods); - } - - private void updateButtonSelection(ModButton button, IReadOnlyList newSelectedMods) - { - foreach (var mod in newSelectedMods) - { - int index = Array.FindIndex(button.Mods, m1 => mod.GetType() == m1.GetType()); - if (index < 0) - continue; - - var buttonMod = button.Mods[index]; - - // as this is likely coming from an external change, ensure the settings of the mod are in sync. - buttonMod.CopyFrom(mod); - - button.SelectAt(index, false); - return; - } - - button.Deselect(); - } - - public ModSection(ModType type) - { - ModType = type; - - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; - - Origin = Anchor.TopCentre; - Anchor = Anchor.TopCentre; - - InternalChildren = new[] - { - header = CreateHeader(type.Humanize(LetterCasing.Title)), - ButtonsContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Spacing = new Vector2(50f, 0f), - Margin = new MarginPadding - { - Top = 20, - }, - AlwaysPresent = true - }, - }; - } - - protected virtual Drawable CreateHeader(string text) => new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = text - }; - - protected virtual ModButton CreateModButton(Mod mod) => new ModButton(mod); - - /// - /// Run any delayed selections (due to animation) immediately to leave mods in a good (final) state. - /// - public void FlushPendingSelections() - { - while (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) - dequeuedAction(); - } - } -} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs deleted file mode 100644 index cf57322594..0000000000 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ /dev/null @@ -1,532 +0,0 @@ -// 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 JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Input.Bindings; -using osu.Game.Resources.Localisation.Web; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens; -using osu.Game.Utils; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods -{ - public abstract class ModSelectOverlay : WaveOverlayContainer - { - public const float HEIGHT = 510; - - protected readonly TriangleButton DeselectAllButton; - protected readonly TriangleButton CustomiseButton; - protected readonly TriangleButton CloseButton; - - protected readonly FillFlowContainer FooterContainer; - - protected override bool BlockNonPositionalInput => false; - - protected override bool DimMainContent => false; - - /// - /// Whether s underneath the same instance should appear as stacked buttons. - /// - protected virtual bool Stacked => true; - - /// - /// Whether configurable s can be configured by the local user. - /// - protected virtual bool AllowConfiguration => true; - - [NotNull] - private Func isValidMod = m => true; - - /// - /// A function that checks whether a given mod is selectable. - /// - [NotNull] - public Func IsValidMod - { - get => isValidMod; - set - { - isValidMod = value ?? throw new ArgumentNullException(nameof(value)); - updateAvailableMods(); - } - } - - protected readonly FillFlowContainer ModSectionsContainer; - - protected readonly ModSettingsContainer ModSettingsContainer; - - [Cached] - public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); - - private Bindable>> availableMods; - - protected Color4 LowMultiplierColour; - protected Color4 HighMultiplierColour; - - private const float content_width = 0.8f; - private const float footer_button_spacing = 20; - - private Sample sampleOn, sampleOff; - - protected ModSelectOverlay() - { - Waves.FirstWaveColour = Color4Extensions.FromHex(@"19b0e2"); - Waves.SecondWaveColour = Color4Extensions.FromHex(@"2280a2"); - Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774"); - Waves.FourthWaveColour = Color4Extensions.FromHex(@"003a4e"); - - RelativeSizeAxes = Axes.X; - Height = HEIGHT; - - Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING }; - - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(36, 50, 68, 255) - }, - new Triangles - { - TriangleScale = 5, - RelativeSizeAxes = Axes.Both, - ColourLight = new Color4(53, 66, 82, 255), - ColourDark = new Color4(41, 54, 70, 255), - }, - }, - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RowDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 90), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(10).Opacity(100), - }, - new FillFlowContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Width = content_width, - Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"Gameplay Mods", - Font = OsuFont.GetFont(size: 22, weight: FontWeight.Bold), - Shadow = true, - Margin = new MarginPadding - { - Bottom = 4, - }, - }, - new OsuTextFlowContainer(text => - { - text.Font = text.Font.With(size: 18); - text.Shadow = true; - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Text = "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play.\nOthers are just for fun.", - }, - }, - }, - }, - }, - }, - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - // Body - new OsuScrollContainer - { - ScrollbarVisible = false, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Vertical = 10, - Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING - }, - Children = new Drawable[] - { - ModSectionsContainer = new FillFlowContainer - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Width = content_width, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, - Children = new[] - { - CreateModSection(ModType.DifficultyReduction).With(s => - { - s.ToggleKeys = new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; - s.Action = modButtonPressed; - }), - CreateModSection(ModType.DifficultyIncrease).With(s => - { - s.ToggleKeys = new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; - s.Action = modButtonPressed; - }), - CreateModSection(ModType.Automation).With(s => - { - s.ToggleKeys = new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; - s.Action = modButtonPressed; - }), - CreateModSection(ModType.Conversion).With(s => - { - s.Action = modButtonPressed; - }), - CreateModSection(ModType.Fun).With(s => - { - s.Action = modButtonPressed; - }), - } - }, - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Padding = new MarginPadding(30), - Width = 0.3f, - Children = new Drawable[] - { - ModSettingsContainer = new ModSettingsContainer - { - Alpha = 0, - SelectedMods = { BindTarget = SelectedMods }, - }, - } - }, - } - }, - }, - new Drawable[] - { - new Container - { - Name = "Footer content", - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(172, 20, 116, 255), - Alpha = 0.5f, - }, - FooterContainer = new FillFlowContainer - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - RelativePositionAxes = Axes.X, - Width = content_width, - Spacing = new Vector2(footer_button_spacing, footer_button_spacing / 2), - Padding = new MarginPadding - { - Vertical = 15, - Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING - }, - Children = new[] - { - DeselectAllButton = new TriangleButton - { - Width = 180, - Text = "Deselect All", - Action = deselectAll, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - CustomiseButton = new TriangleButton - { - Width = 180, - Text = "Customisation", - Action = () => ModSettingsContainer.ToggleVisibility(), - Enabled = { Value = false }, - Alpha = AllowConfiguration ? 1 : 0, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - CloseButton = new TriangleButton - { - Width = 180, - Text = CommonStrings.ButtonsClose, - Action = Hide, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - } - } - }, - } - }, - }, - }, - }; - - ((IBindable)CustomiseButton.Enabled).BindTo(ModSettingsContainer.HasSettingsForSelection); - } - - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuGameBase osu) - { - availableMods = osu.AvailableMods.GetBoundCopy(); - - sampleOn = audio.Samples.Get(@"UI/check-on"); - sampleOff = audio.Samples.Get(@"UI/check-off"); - } - - private void deselectAll() - { - foreach (var section in ModSectionsContainer.Children) - section.DeselectAll(); - - refreshSelectedMods(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - availableMods.BindValueChanged(_ => updateAvailableMods(), true); - - // intentionally bound after the above line to avoid a potential update feedback cycle. - // i haven't actually observed this happening but as updateAvailableMods() changes the selection it is plausible. - SelectedMods.BindValueChanged(_ => updateSelectedButtons()); - } - - protected override void PopOut() - { - base.PopOut(); - - foreach (var section in ModSectionsContainer) - { - section.FlushPendingSelections(); - } - - FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - - foreach (var section in ModSectionsContainer.Children) - { - section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.MoveToX(100f, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - } - } - - protected override void PopIn() - { - base.PopIn(); - - FooterContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - FooterContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - - foreach (var section in ModSectionsContainer.Children) - { - section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), WaveContainer.APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - } - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - // don't absorb control as ToolbarRulesetSelector uses control + number to navigate - if (e.ControlPressed) return false; - - switch (e.Key) - { - case Key.Number1: - DeselectAllButton.TriggerClick(); - return true; - - case Key.Number2: - CloseButton.TriggerClick(); - return true; - } - - return base.OnKeyDown(e); - } - - public override bool OnPressed(KeyBindingPressEvent e) => false; // handled by back button - - private void updateAvailableMods() - { - if (availableMods?.Value == null) - return; - - foreach (var section in ModSectionsContainer.Children) - { - IEnumerable modEnumeration = availableMods.Value[section.ModType]; - - if (!Stacked) - modEnumeration = ModUtils.FlattenMods(modEnumeration); - - section.Mods = modEnumeration.Select(getValidModOrNull).Where(m => m != null).Select(m => m.DeepClone()); - } - - updateSelectedButtons(); - OnAvailableModsChanged(); - } - - /// - /// Returns a valid form of a given if possible, or null otherwise. - /// - /// - /// This is a recursive process during which any invalid mods are culled while preserving structures where possible. - /// - /// The to check. - /// A valid form of if exists, or null otherwise. - [CanBeNull] - private Mod getValidModOrNull([NotNull] Mod mod) - { - if (!(mod is MultiMod multi)) - return IsValidMod(mod) ? mod : null; - - var validSubset = multi.Mods.Select(getValidModOrNull).Where(m => m != null).ToArray(); - - if (validSubset.Length == 0) - return null; - - return validSubset.Length == 1 ? validSubset[0] : new MultiMod(validSubset); - } - - private void updateSelectedButtons() - { - // Enumeration below may update the bindable list. - var selectedMods = SelectedMods.Value.ToList(); - - foreach (var section in ModSectionsContainer.Children) - section.UpdateSelectedButtons(selectedMods); - } - - private void modButtonPressed(Mod selectedMod) - { - if (selectedMod != null) - { - if (State.Value == Visibility.Visible) - Scheduler.AddOnce(playSelectedSound); - - OnModSelected(selectedMod); - - if (selectedMod.RequiresConfiguration && AllowConfiguration) - ModSettingsContainer.Show(); - } - else - { - if (State.Value == Visibility.Visible) - Scheduler.AddOnce(playDeselectedSound); - } - - refreshSelectedMods(); - } - - private void playSelectedSound() => sampleOn?.Play(); - private void playDeselectedSound() => sampleOff?.Play(); - - /// - /// Invoked after has changed. - /// - protected virtual void OnAvailableModsChanged() - { - } - - /// - /// Invoked when a new has been selected. - /// - /// The that has been selected. - protected virtual void OnModSelected(Mod mod) - { - } - - private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); - - /// - /// Creates a that groups s with the same . - /// - /// The of s in the section. - /// The . - protected virtual ModSection CreateModSection(ModType type) => new ModSection(type); - - #region Disposal - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - availableMods?.UnbindAll(); - SelectedMods?.UnbindAll(); - } - - #endregion - } -} diff --git a/osu.Game/Overlays/Mods/ModSettingsContainer.cs b/osu.Game/Overlays/Mods/ModSettingsContainer.cs deleted file mode 100644 index 64d65cab3b..0000000000 --- a/osu.Game/Overlays/Mods/ModSettingsContainer.cs +++ /dev/null @@ -1,111 +0,0 @@ -// 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 osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Configuration; -using osu.Game.Graphics.Containers; -using osu.Game.Rulesets.Mods; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Mods -{ - public class ModSettingsContainer : VisibilityContainer - { - public readonly IBindable> SelectedMods = new Bindable>(Array.Empty()); - - public IBindable HasSettingsForSelection => hasSettingsForSelection; - - private readonly Bindable hasSettingsForSelection = new Bindable(); - - private readonly FillFlowContainer modSettingsContent; - - private readonly Container content; - - private const double transition_duration = 400; - - public ModSettingsContainer() - { - RelativeSizeAxes = Axes.Both; - - Child = content = new Container - { - Masking = true, - CornerRadius = 10, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - X = 1, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(0, 0, 0, 192) - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = modSettingsContent = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Padding = new MarginPadding(20), - } - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - SelectedMods.BindValueChanged(modsChanged, true); - } - - private void modsChanged(ValueChangedEvent> mods) - { - modSettingsContent.Clear(); - - foreach (var mod in mods.NewValue) - { - var settings = mod.CreateSettingsControls().ToList(); - if (settings.Count > 0) - modSettingsContent.Add(new ModControlSection(mod, settings)); - } - - bool hasSettings = modSettingsContent.Count > 0; - - if (!hasSettings) - Hide(); - - hasSettingsForSelection.Value = hasSettings; - } - - protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnHover(HoverEvent e) => true; - - protected override void PopIn() - { - this.FadeIn(transition_duration, Easing.OutQuint); - content.MoveToX(0, transition_duration, Easing.OutQuint); - } - - protected override void PopOut() - { - this.FadeOut(transition_duration, Easing.OutQuint); - content.MoveToX(1, transition_duration, Easing.OutQuint); - } - } -} diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs deleted file mode 100644 index 161f89c2eb..0000000000 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Overlays.Mods -{ - public class UserModSelectOverlay : ModSelectOverlay - { - protected override void OnModSelected(Mod mod) - { - base.OnModSelected(mod); - - foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(mod.IncompatibleMods, true, mod); - } - - protected override ModSection CreateModSection(ModType type) => new UserModSection(type); - - private class UserModSection : ModSection - { - public UserModSection(ModType type) - : base(type) - { - } - - protected override ModButton CreateModButton(Mod mod) => new IncompatibilityDisplayingModButton(mod); - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs deleted file mode 100644 index d5abaaab4e..0000000000 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ /dev/null @@ -1,157 +0,0 @@ -// 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.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Screens.OnlinePlay -{ - /// - /// A used for free-mod selection in online play. - /// - public class FreeModSelectOverlay : ModSelectOverlay - { - protected override bool Stacked => false; - - protected override bool AllowConfiguration => false; - - public new Func IsValidMod - { - get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && m.UserPlayable && value(m); - } - - public FreeModSelectOverlay() - { - IsValidMod = m => true; - - DeselectAllButton.Alpha = 0; - - Drawable selectAllButton; - Drawable deselectAllButton; - - FooterContainer.AddRange(new[] - { - selectAllButton = new TriangleButton - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 180, - Text = "Select All", - Action = selectAll, - }, - // Unlike the base mod select overlay, this button deselects mods instantaneously. - deselectAllButton = new TriangleButton - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 180, - Text = "Deselect All", - Action = deselectAll, - }, - }); - - FooterContainer.SetLayoutPosition(selectAllButton, -2); - FooterContainer.SetLayoutPosition(deselectAllButton, -1); - } - - private void selectAll() - { - foreach (var section in ModSectionsContainer.Children) - section.SelectAll(); - } - - private void deselectAll() - { - foreach (var section in ModSectionsContainer.Children) - section.DeselectAll(); - } - - protected override void OnAvailableModsChanged() - { - base.OnAvailableModsChanged(); - - foreach (var section in ModSectionsContainer.Children) - ((FreeModSection)section).UpdateCheckboxState(); - } - - protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); - - private class FreeModSection : ModSection - { - private HeaderCheckbox checkbox; - - public FreeModSection(ModType type) - : base(type) - { - } - - protected override Drawable CreateHeader(string text) => new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Child = checkbox = new HeaderCheckbox - { - LabelText = text, - Changed = onCheckboxChanged - } - }; - - private void onCheckboxChanged(bool value) - { - if (value) - SelectAll(); - else - DeselectAll(); - } - - protected override void ModButtonStateChanged(Mod mod) - { - base.ModButtonStateChanged(mod); - UpdateCheckboxState(); - } - - public void UpdateCheckboxState() - { - if (!SelectionAnimationRunning) - { - var validButtons = Buttons.Where(b => b.Mod.HasImplementation); - checkbox.Current.Value = validButtons.All(b => b.Selected); - } - } - } - - private class HeaderCheckbox : OsuCheckbox - { - public Action Changed; - - protected override bool PlaySoundsOnUserChange => false; - - public HeaderCheckbox() - : base(false) - - { - } - - protected override void ApplyLabelParameters(SpriteText text) - { - base.ApplyLabelParameters(text); - - text.Font = OsuFont.GetFont(weight: FontWeight.Bold); - } - - protected override void OnUserChange(bool value) - { - base.OnUserChange(value); - Changed?.Invoke(value); - } - } - } -}