mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 04:12:57 +08:00
Merge pull request #18230 from bdach/mod-overlay/data-flow-refactor
Restructure data flow in mod select overlay
This commit is contained in:
commit
678cde3310
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -12,11 +14,9 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets.Catch;
|
|
||||||
using osu.Game.Rulesets.Mania;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Taiko;
|
using osu.Game.Utils;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
@ -41,20 +41,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Child = new ModColumn(modType, false)
|
Child = new ModColumn(modType, false)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre,
|
||||||
|
AvailableMods = getExampleModsFor(modType)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("change ruleset to osu!", () => Ruleset.Value = new OsuRuleset().RulesetInfo);
|
|
||||||
AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
|
|
||||||
AddStep("change ruleset to catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
|
|
||||||
AddStep("change ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMultiSelection()
|
public void TestMultiSelection()
|
||||||
{
|
{
|
||||||
ModColumn column = null;
|
ModColumn column = null!;
|
||||||
AddStep("create content", () => Child = new Container
|
AddStep("create content", () => Child = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -62,7 +58,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Child = column = new ModColumn(ModType.DifficultyIncrease, true)
|
Child = column = new ModColumn(ModType.DifficultyIncrease, true)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre,
|
||||||
|
AvailableMods = getExampleModsFor(ModType.DifficultyIncrease)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -91,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestFiltering()
|
public void TestFiltering()
|
||||||
{
|
{
|
||||||
TestModColumn column = null;
|
TestModColumn column = null!;
|
||||||
|
|
||||||
AddStep("create content", () => Child = new Container
|
AddStep("create content", () => Child = new Container
|
||||||
{
|
{
|
||||||
@ -100,30 +97,31 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Child = column = new TestModColumn(ModType.Fun, true)
|
Child = column = new TestModColumn(ModType.Fun, true)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre,
|
||||||
|
AvailableMods = getExampleModsFor(ModType.Fun)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("set filter", () => column.Filter = mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase));
|
AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)));
|
||||||
AddUntilStep("two panels visible", () => column.ChildrenOfType<ModPanel>().Count(panel => !panel.Filtered.Value) == 2);
|
AddUntilStep("two panels visible", () => column.ChildrenOfType<ModPanel>().Count(panel => !panel.Filtered.Value) == 2);
|
||||||
|
|
||||||
clickToggle();
|
clickToggle();
|
||||||
AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning);
|
AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning);
|
||||||
AddAssert("only visible items selected", () => column.ChildrenOfType<ModPanel>().Where(panel => panel.Active.Value).All(panel => !panel.Filtered.Value));
|
AddAssert("only visible items selected", () => column.ChildrenOfType<ModPanel>().Where(panel => panel.Active.Value).All(panel => !panel.Filtered.Value));
|
||||||
|
|
||||||
AddStep("unset filter", () => column.Filter = null);
|
AddStep("unset filter", () => setFilter(null));
|
||||||
AddUntilStep("all panels visible", () => column.ChildrenOfType<ModPanel>().All(panel => !panel.Filtered.Value));
|
AddUntilStep("all panels visible", () => column.ChildrenOfType<ModPanel>().All(panel => !panel.Filtered.Value));
|
||||||
AddAssert("checkbox not selected", () => !column.ChildrenOfType<OsuCheckbox>().Single().Current.Value);
|
AddAssert("checkbox not selected", () => !column.ChildrenOfType<OsuCheckbox>().Single().Current.Value);
|
||||||
|
|
||||||
AddStep("set filter", () => column.Filter = mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase));
|
AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)));
|
||||||
AddUntilStep("two panels visible", () => column.ChildrenOfType<ModPanel>().Count(panel => !panel.Filtered.Value) == 2);
|
AddUntilStep("two panels visible", () => column.ChildrenOfType<ModPanel>().Count(panel => !panel.Filtered.Value) == 2);
|
||||||
AddAssert("checkbox selected", () => column.ChildrenOfType<OsuCheckbox>().Single().Current.Value);
|
AddAssert("checkbox selected", () => column.ChildrenOfType<OsuCheckbox>().Single().Current.Value);
|
||||||
|
|
||||||
AddStep("filter out everything", () => column.Filter = _ => false);
|
AddStep("filter out everything", () => setFilter(_ => false));
|
||||||
AddUntilStep("no panels visible", () => column.ChildrenOfType<ModPanel>().All(panel => panel.Filtered.Value));
|
AddUntilStep("no panels visible", () => column.ChildrenOfType<ModPanel>().All(panel => panel.Filtered.Value));
|
||||||
AddUntilStep("checkbox hidden", () => !column.ChildrenOfType<OsuCheckbox>().Single().IsPresent);
|
AddUntilStep("checkbox hidden", () => !column.ChildrenOfType<OsuCheckbox>().Single().IsPresent);
|
||||||
|
|
||||||
AddStep("inset filter", () => column.Filter = null);
|
AddStep("inset filter", () => setFilter(null));
|
||||||
AddUntilStep("all panels visible", () => column.ChildrenOfType<ModPanel>().All(panel => !panel.Filtered.Value));
|
AddUntilStep("all panels visible", () => column.ChildrenOfType<ModPanel>().All(panel => !panel.Filtered.Value));
|
||||||
AddUntilStep("checkbox visible", () => column.ChildrenOfType<OsuCheckbox>().Single().IsPresent);
|
AddUntilStep("checkbox visible", () => column.ChildrenOfType<OsuCheckbox>().Single().IsPresent);
|
||||||
|
|
||||||
@ -138,7 +136,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestKeyboardSelection()
|
public void TestKeyboardSelection()
|
||||||
{
|
{
|
||||||
ModColumn column = null;
|
ModColumn column = null!;
|
||||||
AddStep("create content", () => Child = new Container
|
AddStep("create content", () => Child = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -146,7 +144,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Child = column = new ModColumn(ModType.DifficultyReduction, true, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P })
|
Child = column = new ModColumn(ModType.DifficultyReduction, true, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P })
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre,
|
||||||
|
AvailableMods = getExampleModsFor(ModType.DifficultyReduction)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -158,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("press W again", () => InputManager.Key(Key.W));
|
AddStep("press W again", () => InputManager.Key(Key.W));
|
||||||
AddAssert("NF panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
AddAssert("NF panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
||||||
|
|
||||||
AddStep("set filter to NF", () => column.Filter = mod => mod.Acronym == "NF");
|
AddStep("set filter to NF", () => setFilter(mod => mod.Acronym == "NF"));
|
||||||
|
|
||||||
AddStep("press W", () => InputManager.Key(Key.W));
|
AddStep("press W", () => InputManager.Key(Key.W));
|
||||||
AddAssert("NF panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
AddAssert("NF panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
||||||
@ -166,12 +165,18 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("press W again", () => InputManager.Key(Key.W));
|
AddStep("press W again", () => InputManager.Key(Key.W));
|
||||||
AddAssert("NF panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
AddAssert("NF panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
||||||
|
|
||||||
AddStep("filter out everything", () => column.Filter = _ => false);
|
AddStep("filter out everything", () => setFilter(_ => false));
|
||||||
|
|
||||||
AddStep("press W", () => InputManager.Key(Key.W));
|
AddStep("press W", () => InputManager.Key(Key.W));
|
||||||
AddAssert("NF panel not selected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
AddAssert("NF panel not selected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
||||||
|
|
||||||
AddStep("clear filter", () => column.Filter = null);
|
AddStep("clear filter", () => setFilter(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFilter(Func<Mod, bool>? filter)
|
||||||
|
{
|
||||||
|
foreach (var modState in this.ChildrenOfType<ModColumn>().Single().AvailableMods)
|
||||||
|
modState.Filtered.Value = filter?.Invoke(modState.Mod) == false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestModColumn : ModColumn
|
private class TestModColumn : ModColumn
|
||||||
@ -183,5 +188,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ModState[] getExampleModsFor(ModType modType)
|
||||||
|
{
|
||||||
|
return new OsuRuleset().GetModsFor(modType)
|
||||||
|
.SelectMany(ModUtils.FlattenMod)
|
||||||
|
.Select(mod => new ModState(mod))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Tests.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -481,6 +482,21 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddUntilStep("3 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 3);
|
AddUntilStep("3 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestColumnHidingOnRulesetChange()
|
||||||
|
{
|
||||||
|
createScreen();
|
||||||
|
|
||||||
|
changeRuleset(0);
|
||||||
|
AddAssert("5 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 5);
|
||||||
|
|
||||||
|
AddStep("change to ruleset without all mod types", () => Ruleset.Value = TestCustomisableModRuleset.CreateTestRulesetInfo());
|
||||||
|
AddUntilStep("1 column visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 1);
|
||||||
|
|
||||||
|
changeRuleset(0);
|
||||||
|
AddAssert("5 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 5);
|
||||||
|
}
|
||||||
|
|
||||||
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
||||||
() => modSelectOverlay.ChildrenOfType<ModColumn>().Any() && modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
() => modSelectOverlay.ChildrenOfType<ModColumn>().Any() && modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||||
|
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; }
|
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; }
|
||||||
|
|
||||||
|
public IncompatibilityDisplayingModPanel(ModState modState)
|
||||||
|
: base(modState)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public IncompatibilityDisplayingModPanel(Mod mod)
|
public IncompatibilityDisplayingModPanel(Mod mod)
|
||||||
: base(mod)
|
: base(mod)
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,6 @@ using Humanizer;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -25,7 +24,6 @@ using osu.Game.Graphics.Containers;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Utils;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -38,20 +36,30 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
public readonly ModType ModType;
|
public readonly ModType ModType;
|
||||||
|
|
||||||
private Func<Mod, bool>? filter;
|
private IReadOnlyList<ModState> availableMods = Array.Empty<ModState>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A function determining whether each mod in the column should be displayed.
|
/// Sets the list of mods to show in this column.
|
||||||
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
|
|
||||||
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<Mod, bool>? Filter
|
public IReadOnlyList<ModState> AvailableMods
|
||||||
{
|
{
|
||||||
get => filter;
|
get => availableMods;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
filter = value;
|
Debug.Assert(value.All(mod => mod.Mod.Type == ModType));
|
||||||
|
|
||||||
|
availableMods = value;
|
||||||
|
|
||||||
|
foreach (var mod in availableMods)
|
||||||
|
{
|
||||||
|
mod.Active.BindValueChanged(_ => updateState());
|
||||||
|
mod.Filtered.BindValueChanged(_ => updateState());
|
||||||
|
}
|
||||||
|
|
||||||
updateState();
|
updateState();
|
||||||
|
|
||||||
|
if (IsLoaded)
|
||||||
|
asyncLoadPanels();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,44 +68,12 @@ namespace osu.Game.Overlays.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Bindable<bool> Active = new BindableBool(true);
|
public Bindable<bool> Active = new BindableBool(true);
|
||||||
|
|
||||||
private readonly Bindable<bool> allFiltered = new BindableBool();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if all of the panels in this column have been filtered out by the current <see cref="Filter"/>.
|
|
||||||
/// </summary>
|
|
||||||
public IBindable<bool> AllFiltered => allFiltered;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of mods marked as selected in this column.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Note that the mod instances returned by this property are owned solely by this column
|
|
||||||
/// (as in, they are locally-managed clones, to ensure proper isolation from any other external instances).
|
|
||||||
/// </remarks>
|
|
||||||
public IReadOnlyList<Mod> SelectedMods { get; private set; } = Array.Empty<Mod>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked when a mod panel has been selected interactively by the user.
|
|
||||||
/// </summary>
|
|
||||||
public event Action? SelectionChangedByUser;
|
|
||||||
|
|
||||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
|
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
|
||||||
|
|
||||||
protected virtual ModPanel CreateModPanel(Mod mod) => new ModPanel(mod);
|
protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod);
|
||||||
|
|
||||||
private readonly Key[]? toggleKeys;
|
private readonly Key[]? toggleKeys;
|
||||||
|
|
||||||
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All mods that are available for the current ruleset in this particular column.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Note that the mod instances in this list are owned solely by this column
|
|
||||||
/// (as in, they are locally-managed clones, to ensure proper isolation from any other external instances).
|
|
||||||
/// </remarks>
|
|
||||||
private IReadOnlyList<Mod> localAvailableMods = Array.Empty<Mod>();
|
|
||||||
|
|
||||||
private readonly TextFlowContainer headerText;
|
private readonly TextFlowContainer headerText;
|
||||||
private readonly Box headerBackground;
|
private readonly Box headerBackground;
|
||||||
private readonly Container contentContainer;
|
private readonly Container contentContainer;
|
||||||
@ -257,12 +233,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuGameBase game, OverlayColourProvider colourProvider, OsuColour colours)
|
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
availableMods.BindTo(game.AvailableMods);
|
|
||||||
updateLocalAvailableMods(asyncLoadContent: false);
|
|
||||||
availableMods.BindValueChanged(_ => updateLocalAvailableMods(asyncLoadContent: true));
|
|
||||||
|
|
||||||
headerBackground.Colour = accentColour = colours.ForModType(ModType);
|
headerBackground.Colour = accentColour = colours.ForModType(ModType);
|
||||||
|
|
||||||
if (toggleAllCheckbox != null)
|
if (toggleAllCheckbox != null)
|
||||||
@ -280,6 +252,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true);
|
toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true);
|
||||||
|
asyncLoadPanels();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateToggleAllText()
|
private void updateToggleAllText()
|
||||||
@ -288,34 +261,21 @@ namespace osu.Game.Overlays.Mods
|
|||||||
toggleAllCheckbox.LabelText = toggleAllCheckbox.Current.Value ? CommonStrings.DeselectAll : CommonStrings.SelectAll;
|
toggleAllCheckbox.LabelText = toggleAllCheckbox.Current.Value ? CommonStrings.DeselectAll : CommonStrings.SelectAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLocalAvailableMods(bool asyncLoadContent)
|
|
||||||
{
|
|
||||||
var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty<Mod>())
|
|
||||||
.Select(m => m.DeepClone())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (newMods.SequenceEqual(localAvailableMods))
|
|
||||||
return;
|
|
||||||
|
|
||||||
localAvailableMods = newMods;
|
|
||||||
|
|
||||||
if (asyncLoadContent)
|
|
||||||
asyncLoadPanels();
|
|
||||||
else
|
|
||||||
onPanelsLoaded(createPanels());
|
|
||||||
}
|
|
||||||
|
|
||||||
private CancellationTokenSource? cancellationTokenSource;
|
private CancellationTokenSource? cancellationTokenSource;
|
||||||
|
|
||||||
private void asyncLoadPanels()
|
private void asyncLoadPanels()
|
||||||
{
|
{
|
||||||
cancellationTokenSource?.Cancel();
|
cancellationTokenSource?.Cancel();
|
||||||
|
|
||||||
var panels = createPanels();
|
var panels = availableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)));
|
||||||
|
|
||||||
Task? loadTask;
|
Task? loadTask;
|
||||||
|
|
||||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, onPanelsLoaded, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
||||||
|
{
|
||||||
|
panelFlow.ChildrenEnumerable = loaded;
|
||||||
|
updateState();
|
||||||
|
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||||
loadTask.ContinueWith(_ =>
|
loadTask.ContinueWith(_ =>
|
||||||
{
|
{
|
||||||
if (loadTask == latestLoadTask)
|
if (loadTask == latestLoadTask)
|
||||||
@ -323,97 +283,17 @@ namespace osu.Game.Overlays.Mods
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<ModPanel> createPanels()
|
|
||||||
{
|
|
||||||
var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)));
|
|
||||||
return panels;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPanelsLoaded(IEnumerable<ModPanel> loaded)
|
|
||||||
{
|
|
||||||
panelFlow.ChildrenEnumerable = loaded;
|
|
||||||
|
|
||||||
updateState();
|
|
||||||
|
|
||||||
foreach (var panel in panelFlow)
|
|
||||||
{
|
|
||||||
panel.Active.BindValueChanged(_ => panelStateChanged(panel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
foreach (var panel in panelFlow)
|
Alpha = availableMods.All(mod => mod.Filtered.Value) ? 0 : 1;
|
||||||
{
|
|
||||||
panel.Active.Value = SelectedMods.Contains(panel.Mod);
|
|
||||||
panel.ApplyFilter(Filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
allFiltered.Value = panelFlow.All(panel => panel.Filtered.Value);
|
|
||||||
|
|
||||||
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
|
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
|
||||||
{
|
{
|
||||||
toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0;
|
toggleAllCheckbox.Alpha = availableMods.Any(panel => !panel.Filtered.Value) ? 1 : 0;
|
||||||
toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value);
|
toggleAllCheckbox.Current.Value = availableMods.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This flag helps to determine the source of changes to <see cref="SelectedMods"/>.
|
|
||||||
/// If the value is false, then <see cref="SelectedMods"/> are changing due to a user selection on the UI.
|
|
||||||
/// If the value is true, then <see cref="SelectedMods"/> are changing due to an external <see cref="SetSelection"/> call.
|
|
||||||
/// </summary>
|
|
||||||
private bool externalSelectionUpdateInProgress;
|
|
||||||
|
|
||||||
private void panelStateChanged(ModPanel panel)
|
|
||||||
{
|
|
||||||
if (externalSelectionUpdateInProgress)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var newSelectedMods = panel.Active.Value
|
|
||||||
? SelectedMods.Append(panel.Mod)
|
|
||||||
: SelectedMods.Except(panel.Mod.Yield());
|
|
||||||
|
|
||||||
SelectedMods = newSelectedMods.ToArray();
|
|
||||||
updateState();
|
|
||||||
SelectionChangedByUser?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adjusts the set of selected mods in this column to match the passed in <paramref name="mods"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method exists to be able to receive mod instances that come from potentially-external sources and to copy the changes across to this column's state.
|
|
||||||
/// <see cref="ModSelectOverlay"/> uses this to substitute any external mod references in <see cref="ModSelectOverlay.SelectedMods"/>
|
|
||||||
/// to references that are owned by this column.
|
|
||||||
/// </remarks>
|
|
||||||
internal void SetSelection(IReadOnlyList<Mod> mods)
|
|
||||||
{
|
|
||||||
externalSelectionUpdateInProgress = true;
|
|
||||||
|
|
||||||
var newSelection = new List<Mod>();
|
|
||||||
|
|
||||||
foreach (var mod in localAvailableMods)
|
|
||||||
{
|
|
||||||
var matchingSelectedMod = mods.SingleOrDefault(selected => selected.GetType() == mod.GetType());
|
|
||||||
|
|
||||||
if (matchingSelectedMod != null)
|
|
||||||
{
|
|
||||||
mod.CopyFrom(matchingSelectedMod);
|
|
||||||
newSelection.Add(mod);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mod.ResetSettingsToDefaults();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectedMods = newSelection;
|
|
||||||
updateState();
|
|
||||||
|
|
||||||
externalSelectionUpdateInProgress = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Bulk select / deselect
|
#region Bulk select / deselect
|
||||||
|
|
||||||
private const double initial_multiple_selection_delay = 120;
|
private const double initial_multiple_selection_delay = 120;
|
||||||
@ -455,7 +335,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
pendingSelectionOperations.Clear();
|
pendingSelectionOperations.Clear();
|
||||||
|
|
||||||
foreach (var button in panelFlow.Where(b => !b.Active.Value && !b.Filtered.Value))
|
foreach (var button in availableMods.Where(b => !b.Active.Value && !b.Filtered.Value))
|
||||||
pendingSelectionOperations.Enqueue(() => button.Active.Value = true);
|
pendingSelectionOperations.Enqueue(() => button.Active.Value = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,7 +346,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
pendingSelectionOperations.Clear();
|
pendingSelectionOperations.Clear();
|
||||||
|
|
||||||
foreach (var button in panelFlow.Where(b => b.Active.Value && !b.Filtered.Value))
|
foreach (var button in availableMods.Where(b => b.Active.Value && !b.Filtered.Value))
|
||||||
pendingSelectionOperations.Enqueue(() => button.Active.Value = false);
|
pendingSelectionOperations.Enqueue(() => button.Active.Value = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,10 +433,10 @@ namespace osu.Game.Overlays.Mods
|
|||||||
int index = Array.IndexOf(toggleKeys, e.Key);
|
int index = Array.IndexOf(toggleKeys, e.Key);
|
||||||
if (index < 0) return false;
|
if (index < 0) return false;
|
||||||
|
|
||||||
var panel = panelFlow.ElementAtOrDefault(index);
|
var modState = availableMods.ElementAtOrDefault(index);
|
||||||
if (panel == null || panel.Filtered.Value) return false;
|
if (modState == null || modState.Filtered.Value) return false;
|
||||||
|
|
||||||
panel.Active.Toggle();
|
modState.Active.Toggle();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,9 +28,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
public class ModPanel : OsuClickableContainer
|
public class ModPanel : OsuClickableContainer
|
||||||
{
|
{
|
||||||
public Mod Mod { get; }
|
public Mod Mod => modState.Mod;
|
||||||
public BindableBool Active { get; } = new BindableBool();
|
public BindableBool Active => modState.Active;
|
||||||
public BindableBool Filtered { get; } = new BindableBool();
|
public BindableBool Filtered => modState.Filtered;
|
||||||
|
|
||||||
|
private readonly ModState modState;
|
||||||
|
|
||||||
protected readonly Box Background;
|
protected readonly Box Background;
|
||||||
protected readonly Container SwitchContainer;
|
protected readonly Container SwitchContainer;
|
||||||
@ -55,9 +57,9 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private Sample? sampleOff;
|
private Sample? sampleOff;
|
||||||
private Sample? sampleOn;
|
private Sample? sampleOn;
|
||||||
|
|
||||||
public ModPanel(Mod mod)
|
public ModPanel(ModState modState)
|
||||||
{
|
{
|
||||||
Mod = mod;
|
this.modState = modState;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = 42;
|
Height = 42;
|
||||||
@ -79,7 +81,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
SwitchContainer = new Container
|
SwitchContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Child = new ModSwitchSmall(mod)
|
Child = new ModSwitchSmall(Mod)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -115,7 +117,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = mod.Name,
|
Text = Mod.Name,
|
||||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
@ -125,7 +127,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
},
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = mod.Description,
|
Text = Mod.Description,
|
||||||
Font = OsuFont.Default.With(size: 12),
|
Font = OsuFont.Default.With(size: 12),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
Truncate = true,
|
||||||
@ -141,6 +143,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Action = Active.Toggle;
|
Action = Active.Toggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ModPanel(Mod mod)
|
||||||
|
: this(new ModState(mod))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Lists;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -22,6 +21,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -47,9 +47,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
isValidMod = value ?? throw new ArgumentNullException(nameof(value));
|
isValidMod = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
filterMods();
|
||||||
if (IsLoaded)
|
|
||||||
updateAvailableMods();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +62,10 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() => createDefaultFooterButtons();
|
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() => createDefaultFooterButtons();
|
||||||
|
|
||||||
|
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||||
|
private readonly Dictionary<ModType, IReadOnlyList<ModState>> localAvailableMods = new Dictionary<ModType, IReadOnlyList<ModState>>();
|
||||||
|
private IEnumerable<ModState> allLocalAvailableMods => localAvailableMods.SelectMany(pair => pair.Value);
|
||||||
|
|
||||||
private readonly BindableBool customisationVisible = new BindableBool();
|
private readonly BindableBool customisationVisible = new BindableBool();
|
||||||
|
|
||||||
private ModSettingsArea modSettingsArea = null!;
|
private ModSettingsArea modSettingsArea = null!;
|
||||||
@ -82,7 +84,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuGameBase game, OsuColour colours)
|
||||||
{
|
{
|
||||||
Header.Title = ModSelectOverlayStrings.ModSelectTitle;
|
Header.Title = ModSelectOverlayStrings.ModSelectTitle;
|
||||||
Header.Description = ModSelectOverlayStrings.ModSelectDescription;
|
Header.Description = ModSelectOverlayStrings.ModSelectDescription;
|
||||||
@ -184,10 +186,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
LighterColour = colours.Pink1
|
LighterColour = colours.Pink1
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
availableMods.BindTo(game.AvailableMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
// this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly.
|
||||||
|
availableMods.BindValueChanged(_ => createLocalMods(), true);
|
||||||
|
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true);
|
State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true);
|
||||||
@ -201,18 +208,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
updateMultiplier();
|
updateMultiplier();
|
||||||
updateCustomisation(val);
|
updateCustomisation(val);
|
||||||
updateSelectionFromBindable();
|
updateFromExternalSelection();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
foreach (var column in columnFlow.Columns)
|
|
||||||
{
|
|
||||||
column.SelectionChangedByUser += updateBindableFromSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||||
|
|
||||||
updateAvailableMods();
|
|
||||||
|
|
||||||
// Start scrolled slightly to the right to give the user a sense that
|
// Start scrolled slightly to the right to give the user a sense that
|
||||||
// there is more horizontal content available.
|
// there is more horizontal content available.
|
||||||
ScheduleAfterChildren(() =>
|
ScheduleAfterChildren(() =>
|
||||||
@ -244,7 +244,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
var column = CreateModColumn(modType, toggleKeys).With(column =>
|
var column = CreateModColumn(modType, toggleKeys).With(column =>
|
||||||
{
|
{
|
||||||
column.Filter = IsValidMod;
|
|
||||||
// spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden.
|
// spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden.
|
||||||
column.Margin = new MarginPadding { Right = 10 };
|
column.Margin = new MarginPadding { Right = 10 };
|
||||||
});
|
});
|
||||||
@ -272,6 +271,34 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private void createLocalMods()
|
||||||
|
{
|
||||||
|
localAvailableMods.Clear();
|
||||||
|
|
||||||
|
foreach (var (modType, mods) in availableMods.Value)
|
||||||
|
{
|
||||||
|
var modStates = mods.SelectMany(ModUtils.FlattenMod)
|
||||||
|
.Select(mod => new ModState(mod.DeepClone()))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var modState in modStates)
|
||||||
|
modState.Active.BindValueChanged(_ => updateFromInternalSelection());
|
||||||
|
|
||||||
|
localAvailableMods[modType] = modStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterMods();
|
||||||
|
|
||||||
|
foreach (var column in columnFlow.Columns)
|
||||||
|
column.AvailableMods = localAvailableMods.GetValueOrDefault(column.ModType, Array.Empty<ModState>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filterMods()
|
||||||
|
{
|
||||||
|
foreach (var modState in allLocalAvailableMods)
|
||||||
|
modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateMultiplier()
|
private void updateMultiplier()
|
||||||
{
|
{
|
||||||
if (multiplierDisplay == null)
|
if (multiplierDisplay == null)
|
||||||
@ -285,12 +312,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
multiplierDisplay.Current.Value = multiplier;
|
multiplierDisplay.Current.Value = multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAvailableMods()
|
|
||||||
{
|
|
||||||
foreach (var column in columnFlow.Columns)
|
|
||||||
column.Filter = m => m.HasImplementation && isValidMod.Invoke(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCustomisation(ValueChangedEvent<IReadOnlyList<Mod>> valueChangedEvent)
|
private void updateCustomisation(ValueChangedEvent<IReadOnlyList<Mod>> valueChangedEvent)
|
||||||
{
|
{
|
||||||
if (customisationButton == null)
|
if (customisationButton == null)
|
||||||
@ -339,26 +360,53 @@ namespace osu.Game.Overlays.Mods
|
|||||||
TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic);
|
TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSelectionFromBindable()
|
/// <summary>
|
||||||
{
|
/// This flag helps to determine the source of changes to <see cref="SelectedMods"/>.
|
||||||
// `SelectedMods` may contain mod references that come from external sources.
|
/// If the value is false, then <see cref="SelectedMods"/> are changing due to a user selection on the UI.
|
||||||
// to ensure isolation, first pull in the potentially-external change into the mod columns...
|
/// If the value is true, then <see cref="SelectedMods"/> are changing due to an external <see cref="SelectedMods"/> change.
|
||||||
foreach (var column in columnFlow.Columns)
|
/// </summary>
|
||||||
column.SetSelection(SelectedMods.Value);
|
private bool externalSelectionUpdateInProgress;
|
||||||
|
|
||||||
// and then, when done, replace the potentially-external mod references in `SelectedMods` with ones we own.
|
private void updateFromExternalSelection()
|
||||||
updateBindableFromSelection();
|
{
|
||||||
|
if (externalSelectionUpdateInProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
externalSelectionUpdateInProgress = true;
|
||||||
|
|
||||||
|
var newSelection = new List<Mod>();
|
||||||
|
|
||||||
|
foreach (var modState in allLocalAvailableMods)
|
||||||
|
{
|
||||||
|
var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType());
|
||||||
|
|
||||||
|
if (matchingSelectedMod != null)
|
||||||
|
{
|
||||||
|
modState.Mod.CopyFrom(matchingSelectedMod);
|
||||||
|
modState.Active.Value = true;
|
||||||
|
newSelection.Add(modState.Mod);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
modState.Mod.ResetSettingsToDefaults();
|
||||||
|
modState.Active.Value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedMods.Value = newSelection;
|
||||||
|
|
||||||
|
externalSelectionUpdateInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBindableFromSelection()
|
private void updateFromInternalSelection()
|
||||||
{
|
{
|
||||||
var candidateSelection = columnFlow.Columns.SelectMany(column => column.SelectedMods).ToArray();
|
if (externalSelectionUpdateInProgress)
|
||||||
|
|
||||||
// the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion.
|
|
||||||
// TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6
|
|
||||||
if (candidateSelection.SequenceEqual(SelectedMods.Value, new FuncEqualityComparer<Mod>(ReferenceEquals)))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var candidateSelection = allLocalAvailableMods.Where(modState => modState.Active.Value)
|
||||||
|
.Select(modState => modState.Mod)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
|
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,10 +431,12 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
var column = columnFlow[i].Column;
|
var column = columnFlow[i].Column;
|
||||||
|
|
||||||
double delay = column.AllFiltered.Value ? 0 : nonFilteredColumnCount * 30;
|
bool allFiltered = column.AvailableMods.All(modState => modState.Filtered.Value);
|
||||||
double duration = column.AllFiltered.Value ? 0 : fade_in_duration;
|
|
||||||
|
double delay = allFiltered ? 0 : nonFilteredColumnCount * 30;
|
||||||
|
double duration = allFiltered ? 0 : fade_in_duration;
|
||||||
float startingYPosition = 0;
|
float startingYPosition = 0;
|
||||||
if (!column.AllFiltered.Value)
|
if (!allFiltered)
|
||||||
startingYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance;
|
startingYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance;
|
||||||
|
|
||||||
column.TopLevelContent
|
column.TopLevelContent
|
||||||
@ -395,7 +445,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
.MoveToY(0, duration, Easing.OutQuint)
|
.MoveToY(0, duration, Easing.OutQuint)
|
||||||
.FadeIn(duration, Easing.OutQuint);
|
.FadeIn(duration, Easing.OutQuint);
|
||||||
|
|
||||||
if (!column.AllFiltered.Value)
|
if (!allFiltered)
|
||||||
nonFilteredColumnCount += 1;
|
nonFilteredColumnCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -416,9 +466,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
var column = columnFlow[i].Column;
|
var column = columnFlow[i].Column;
|
||||||
|
|
||||||
double duration = column.AllFiltered.Value ? 0 : fade_out_duration;
|
bool allFiltered = column.AvailableMods.All(modState => modState.Filtered.Value);
|
||||||
|
|
||||||
|
double duration = allFiltered ? 0 : fade_out_duration;
|
||||||
float newYPosition = 0;
|
float newYPosition = 0;
|
||||||
if (!column.AllFiltered.Value)
|
if (!allFiltered)
|
||||||
newYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance;
|
newYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance;
|
||||||
|
|
||||||
column.FlushPendingSelections();
|
column.FlushPendingSelections();
|
||||||
@ -426,7 +478,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
.MoveToY(newYPosition, duration, Easing.OutQuint)
|
.MoveToY(newYPosition, duration, Easing.OutQuint)
|
||||||
.FadeOut(duration, Easing.OutQuint);
|
.FadeOut(duration, Easing.OutQuint);
|
||||||
|
|
||||||
if (!column.AllFiltered.Value)
|
if (!allFiltered)
|
||||||
nonFilteredColumnCount += 1;
|
nonFilteredColumnCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -570,8 +622,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
Active.BindValueChanged(_ => updateState());
|
|
||||||
Column.AllFiltered.BindValueChanged(_ => updateState(), true);
|
Active.BindValueChanged(_ => updateState(), true);
|
||||||
FinishTransforms();
|
FinishTransforms();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -581,8 +633,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Colour4 targetColour;
|
Colour4 targetColour;
|
||||||
|
|
||||||
Column.Alpha = Column.AllFiltered.Value ? 0 : 1;
|
|
||||||
|
|
||||||
if (Column.Active.Value)
|
if (Column.Active.Value)
|
||||||
targetColour = Colour4.White;
|
targetColour = Colour4.White;
|
||||||
else
|
else
|
||||||
|
35
osu.Game/Overlays/Mods/ModState.cs
Normal file
35
osu.Game/Overlays/Mods/ModState.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper class used to store the current state of a mod shown on the <see cref="ModSelectOverlay"/>.
|
||||||
|
/// Used primarily to decouple data from drawable logic.
|
||||||
|
/// </summary>
|
||||||
|
public class ModState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The mod that whose state this instance describes.
|
||||||
|
/// </summary>
|
||||||
|
public Mod Mod { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the mod is currently selected.
|
||||||
|
/// </summary>
|
||||||
|
public BindableBool Active { get; } = new BindableBool();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the mod is currently filtered out due to not matching imposed criteria.
|
||||||
|
/// </summary>
|
||||||
|
public BindableBool Filtered { get; } = new BindableBool();
|
||||||
|
|
||||||
|
public ModState(Mod mod)
|
||||||
|
{
|
||||||
|
Mod = mod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ModPanel CreateModPanel(Mod mod) => new IncompatibilityDisplayingModPanel(mod);
|
protected override ModPanel CreateModPanel(ModState modState) => new IncompatibilityDisplayingModPanel(modState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user