1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 02:33:02 +08:00

Merge pull request #18419 from bdach/mod-overlay/enable-disable-select-deselect

Disable select/deselect all buttons on mod overlay when they have no effect
This commit is contained in:
Dan Balasescu 2022-05-26 12:26:10 +09:00 committed by GitHub
commit c524b665ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 156 additions and 60 deletions

View File

@ -9,7 +9,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -73,19 +72,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createFreeModSelect(); createFreeModSelect();
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
AddStep("click select all button", () => AddStep("click select all button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().ElementAt(1)); InputManager.MoveMouseTo(this.ChildrenOfType<SelectAllModsButton>().Single());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddUntilStep("all mods selected", assertAllAvailableModsSelected); AddUntilStep("all mods selected", assertAllAvailableModsSelected);
AddAssert("select all button disabled", () => !this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
AddStep("click deselect all button", () => AddStep("click deselect all button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last()); InputManager.MoveMouseTo(this.ChildrenOfType<DeselectAllModsButton>().Single());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any()); AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any());
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
} }
private void createFreeModSelect() private void createFreeModSelect()

View File

@ -435,15 +435,19 @@ namespace osu.Game.Tests.Visual.UserInterface
createScreen(); createScreen();
changeRuleset(0); changeRuleset(0);
AddAssert("deselect all button disabled", () => !this.ChildrenOfType<DeselectAllModsButton>().Single().Enabled.Value);
AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() });
AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2); AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
AddAssert("deselect all button enabled", () => this.ChildrenOfType<DeselectAllModsButton>().Single().Enabled.Value);
AddStep("click deselect all button", () => AddStep("click deselect all button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last()); InputManager.MoveMouseTo(this.ChildrenOfType<DeselectAllModsButton>().Single());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any());
AddAssert("deselect all button disabled", () => !this.ChildrenOfType<DeselectAllModsButton>().Single().Enabled.Value);
} }
[Test] [Test]

View File

@ -0,0 +1,54 @@
// 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 System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Overlays.Mods
{
public class DeselectAllModsButton : ShearedButton, IKeyBindingHandler<GlobalAction>
{
private readonly Bindable<IReadOnlyList<Mod>> selectedMods = new Bindable<IReadOnlyList<Mod>>();
public DeselectAllModsButton(ModSelectOverlay modSelectOverlay)
: base(ModSelectOverlay.BUTTON_WIDTH)
{
Text = CommonStrings.DeselectAll;
Action = modSelectOverlay.DeselectAll;
selectedMods.BindTo(modSelectOverlay.SelectedMods);
}
protected override void LoadComplete()
{
base.LoadComplete();
selectedMods.BindValueChanged(_ => updateEnabledState(), true);
}
private void updateEnabledState()
{
Enabled.Value = selectedMods.Value.Any();
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (e.Repeat || e.Action != GlobalAction.DeselectAllMods)
return false;
TriggerClick();
return true;
}
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
}
}
}

View File

@ -29,11 +29,20 @@ namespace osu.Game.Overlays.Mods
{ {
public abstract class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler public abstract class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler
{ {
protected const int BUTTON_WIDTH = 200; public const int BUTTON_WIDTH = 200;
[Cached] [Cached]
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>()); public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary>
/// Contains a dictionary with the current <see cref="ModState"/> of all mods applicable for the current ruleset.
/// </summary>
/// <remarks>
/// Contrary to <see cref="OsuGameBase.AvailableMods"/> and <see cref="globalAvailableMods"/>, the <see cref="Mod"/> instances
/// inside the <see cref="ModState"/> objects are owned solely by this <see cref="ModSelectOverlay"/> instance.
/// </remarks>
public Bindable<Dictionary<ModType, IReadOnlyList<ModState>>> AvailableMods { get; } = new Bindable<Dictionary<ModType, IReadOnlyList<ModState>>>(new Dictionary<ModType, IReadOnlyList<ModState>>());
private Func<Mod, bool> isValidMod = m => true; private Func<Mod, bool> isValidMod = m => true;
/// <summary> /// <summary>
@ -76,16 +85,12 @@ namespace osu.Game.Overlays.Mods
}; };
} }
yield return deselectAllButton = new ShearedButton(BUTTON_WIDTH) yield return new DeselectAllModsButton(this);
{
Text = CommonStrings.DeselectAll,
Action = DeselectAll
};
} }
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>(); private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = 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 IEnumerable<ModState> allAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
private readonly BindableBool customisationVisible = new BindableBool(); private readonly BindableBool customisationVisible = new BindableBool();
@ -98,7 +103,6 @@ namespace osu.Game.Overlays.Mods
private DifficultyMultiplierDisplay? multiplierDisplay; private DifficultyMultiplierDisplay? multiplierDisplay;
private ShearedToggleButton? customisationButton; private ShearedToggleButton? customisationButton;
private ShearedButton? deselectAllButton;
protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
: base(colourScheme) : base(colourScheme)
@ -209,13 +213,13 @@ namespace osu.Game.Overlays.Mods
}) })
}; };
availableMods.BindTo(game.AvailableMods); globalAvailableMods.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. // 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); globalAvailableMods.BindValueChanged(_ => createLocalMods(), true);
base.LoadComplete(); base.LoadComplete();
@ -247,7 +251,7 @@ namespace osu.Game.Overlays.Mods
/// <summary> /// <summary>
/// Select all visible mods in all columns. /// Select all visible mods in all columns.
/// </summary> /// </summary>
protected void SelectAll() public void SelectAll()
{ {
foreach (var column in columnFlow.Columns) foreach (var column in columnFlow.Columns)
column.SelectAll(); column.SelectAll();
@ -256,7 +260,7 @@ namespace osu.Game.Overlays.Mods
/// <summary> /// <summary>
/// Deselect all visible mods in all columns. /// Deselect all visible mods in all columns.
/// </summary> /// </summary>
protected void DeselectAll() public void DeselectAll()
{ {
foreach (var column in columnFlow.Columns) foreach (var column in columnFlow.Columns)
column.DeselectAll(); column.DeselectAll();
@ -280,9 +284,9 @@ namespace osu.Game.Overlays.Mods
private void createLocalMods() private void createLocalMods()
{ {
localAvailableMods.Clear(); var newLocalAvailableMods = new Dictionary<ModType, IReadOnlyList<ModState>>();
foreach (var (modType, mods) in availableMods.Value) foreach (var (modType, mods) in globalAvailableMods.Value)
{ {
var modStates = mods.SelectMany(ModUtils.FlattenMod) var modStates = mods.SelectMany(ModUtils.FlattenMod)
.Select(mod => new ModState(mod.DeepClone())) .Select(mod => new ModState(mod.DeepClone()))
@ -291,18 +295,19 @@ namespace osu.Game.Overlays.Mods
foreach (var modState in modStates) foreach (var modState in modStates)
modState.Active.BindValueChanged(_ => updateFromInternalSelection()); modState.Active.BindValueChanged(_ => updateFromInternalSelection());
localAvailableMods[modType] = modStates; newLocalAvailableMods[modType] = modStates;
} }
AvailableMods.Value = newLocalAvailableMods;
filterMods(); filterMods();
foreach (var column in columnFlow.Columns) foreach (var column in columnFlow.Columns)
column.AvailableMods = localAvailableMods.GetValueOrDefault(column.ModType, Array.Empty<ModState>()); column.AvailableMods = AvailableMods.Value.GetValueOrDefault(column.ModType, Array.Empty<ModState>());
} }
private void filterMods() private void filterMods()
{ {
foreach (var modState in allLocalAvailableMods) foreach (var modState in allAvailableMods)
modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod); modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod);
} }
@ -383,7 +388,7 @@ namespace osu.Game.Overlays.Mods
var newSelection = new List<Mod>(); var newSelection = new List<Mod>();
foreach (var modState in allLocalAvailableMods) foreach (var modState in allAvailableMods)
{ {
var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType()); var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType());
@ -410,9 +415,9 @@ namespace osu.Game.Overlays.Mods
if (externalSelectionUpdateInProgress) if (externalSelectionUpdateInProgress)
return; return;
var candidateSelection = allLocalAvailableMods.Where(modState => modState.Active.Value) var candidateSelection = allAvailableMods.Where(modState => modState.Active.Value)
.Select(modState => modState.Mod) .Select(modState => modState.Mod)
.ToArray(); .ToArray();
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection); SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
} }
@ -514,10 +519,6 @@ namespace osu.Game.Overlays.Mods
hideOverlay(true); hideOverlay(true);
return true; return true;
} }
case GlobalAction.DeselectAllMods:
deselectAllButton?.TriggerClick();
return true;
} }
return base.OnPressed(e); return base.OnPressed(e);

View File

@ -0,0 +1,61 @@
// 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 System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay;
namespace osu.Game.Overlays.Mods
{
public class SelectAllModsButton : ShearedButton, IKeyBindingHandler<PlatformAction>
{
private readonly Bindable<IReadOnlyList<Mod>> selectedMods = new Bindable<IReadOnlyList<Mod>>();
private readonly Bindable<Dictionary<ModType, IReadOnlyList<ModState>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<ModState>>>();
public SelectAllModsButton(FreeModSelectOverlay modSelectOverlay)
: base(ModSelectOverlay.BUTTON_WIDTH)
{
Text = CommonStrings.SelectAll;
Action = modSelectOverlay.SelectAll;
selectedMods.BindTo(modSelectOverlay.SelectedMods);
availableMods.BindTo(modSelectOverlay.AvailableMods);
}
protected override void LoadComplete()
{
base.LoadComplete();
selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState));
availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState));
updateEnabledState();
}
private void updateEnabledState()
{
Enabled.Value = availableMods.Value
.SelectMany(pair => pair.Value)
.Any(modState => !modState.Active.Value && !modState.Filtered.Value);
}
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
{
if (e.Repeat || e.Action != PlatformAction.SelectAll)
return false;
TriggerClick();
return true;
}
public void OnReleased(KeyBindingReleaseEvent<PlatformAction> e)
{
}
}
}

View File

@ -6,18 +6,14 @@ using osu.Game.Overlays;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osuTK.Input; using osuTK.Input;
using osu.Game.Localisation;
namespace osu.Game.Screens.OnlinePlay namespace osu.Game.Screens.OnlinePlay
{ {
public class FreeModSelectOverlay : ModSelectOverlay, IKeyBindingHandler<PlatformAction> public class FreeModSelectOverlay : ModSelectOverlay
{ {
protected override bool ShowTotalMultiplier => false; protected override bool ShowTotalMultiplier => false;
@ -29,8 +25,6 @@ namespace osu.Game.Screens.OnlinePlay
set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m);
} }
private ShearedButton selectAllButton;
public FreeModSelectOverlay() public FreeModSelectOverlay()
: base(OverlayColourScheme.Plum) : base(OverlayColourScheme.Plum)
{ {
@ -40,31 +34,10 @@ namespace osu.Game.Screens.OnlinePlay
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys); protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys);
protected override IEnumerable<ShearedButton> CreateFooterButtons() => base.CreateFooterButtons().Prepend( protected override IEnumerable<ShearedButton> CreateFooterButtons() => base.CreateFooterButtons().Prepend(
selectAllButton = new ShearedButton(BUTTON_WIDTH) new SelectAllModsButton(this)
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Text = CommonStrings.SelectAll,
Action = SelectAll
}); });
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
{
if (e.Repeat)
return false;
switch (e.Action)
{
case PlatformAction.SelectAll:
selectAllButton.TriggerClick();
return true;
}
return false;
}
public void OnReleased(KeyBindingReleaseEvent<PlatformAction> e)
{
}
} }
} }