diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 0401d516ed..71a974da7d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -1,6 +1,7 @@ // 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 NUnit.Framework; using osu.Framework.Allocation; @@ -83,5 +84,54 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); } + + [Test] + public void TestFiltering() + { + TestModColumn column = null; + + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = column = new TestModColumn(ModType.Fun, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }); + + AddStep("set filter", () => column.Filter = mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); + + clickToggle(); + AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning); + AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => !panel.Filtered.Value)); + + AddStep("unset filter", () => column.Filter = null); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); + AddAssert("checkbox not selected", () => !column.ChildrenOfType().Single().Current.Value); + + AddStep("set filter", () => column.Filter = mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); + AddAssert("checkbox selected", () => column.ChildrenOfType().Single().Current.Value); + + void clickToggle() => AddStep("click toggle", () => + { + var checkbox = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(checkbox); + InputManager.Click(MouseButton.Left); + }); + } + + private class TestModColumn : ModColumn + { + public new bool SelectionAnimationRunning => base.SelectionAnimationRunning; + + public TestModColumn(ModType modType, bool allowBulkSelection) + : base(modType, allowBulkSelection) + { + } + } } } diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 15b2874edb..eae57f3574 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -28,6 +28,18 @@ namespace osu.Game.Overlays.Mods { public class ModColumn : CompositeDrawable { + private Func? filter; + + public Func? Filter + { + get => filter; + set + { + filter = value; + updateFilter(); + } + } + private readonly ModType modType; private readonly Bindable>> availableMods = new Bindable>>(); @@ -220,9 +232,12 @@ namespace osu.Game.Overlays.Mods LoadComponentsAsync(panels, loaded => { panelFlow.ChildrenEnumerable = loaded; + foreach (var panel in panelFlow) panel.Active.BindValueChanged(_ => updateToggleState()); updateToggleState(); + + updateFilter(); }, (cancellationTokenSource = new CancellationTokenSource()).Token); } @@ -235,6 +250,8 @@ namespace osu.Game.Overlays.Mods private readonly Queue pendingSelectionOperations = new Queue(); + protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; + protected override void Update() { base.Update(); @@ -260,8 +277,8 @@ namespace osu.Game.Overlays.Mods private void updateToggleState() { - if (toggleAllCheckbox != null && pendingSelectionOperations.Count == 0) - toggleAllCheckbox.Current.Value = panelFlow.All(panel => panel.Active.Value); + if (toggleAllCheckbox != null && !SelectionAnimationRunning) + toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value); } /// @@ -271,7 +288,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in panelFlow.Where(b => !b.Active.Value)) + foreach (var button in panelFlow.Where(b => !b.Active.Value && !b.Filtered.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = true); } @@ -282,7 +299,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in panelFlow.Where(b => b.Active.Value)) + foreach (var button in panelFlow.Where(b => b.Active.Value && !b.Filtered.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = false); } @@ -349,5 +366,17 @@ namespace osu.Game.Overlays.Mods } #endregion + + #region Filtering support + + private void updateFilter() + { + foreach (var modPanel in panelFlow) + modPanel.ApplyFilter(Filter); + + updateToggleState(); + } + + #endregion } } diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 446ebe12c5..7e4d19850d 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -28,6 +29,7 @@ namespace osu.Game.Overlays.Mods { public Mod Mod { get; } public BindableBool Active { get; } = new BindableBool(); + public BindableBool Filtered { get; } = new BindableBool(); protected readonly Box Background; protected readonly Container SwitchContainer; @@ -157,6 +159,7 @@ namespace osu.Game.Overlays.Mods playStateChangeSamples(); UpdateState(); }); + Filtered.BindValueChanged(_ => updateFilterState()); UpdateState(); FinishTransforms(true); @@ -235,5 +238,19 @@ namespace osu.Game.Overlays.Mods TextBackground.FadeColour(textBackgroundColour, transitionDuration, Easing.OutQuint); TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } + + #region Filtering support + + public void ApplyFilter(Func? filter) + { + Filtered.Value = filter != null && !filter.Invoke(Mod); + } + + private void updateFilterState() + { + this.FadeTo(Filtered.Value ? 0 : 1); + } + + #endregion } }