1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 03:25:11 +08:00

Merge pull request #18130 from bdach/mod-overlay/bulk-select-buttons

Add back select/deselect all mods buttons to new mod select design
This commit is contained in:
Dean Herbert 2022-05-07 17:32:10 +09:00 committed by GitHub
commit 2752bdf04f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 166 additions and 31 deletions

View File

@ -1,19 +1,32 @@
// 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.
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
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.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
public class TestSceneFreeModSelectScreen : MultiplayerTestScene public class TestSceneFreeModSelectScreen : MultiplayerTestScene
{ {
private FreeModSelectScreen freeModSelectScreen; private FreeModSelectScreen freeModSelectScreen;
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGameBase)
{
availableMods.BindTo(osuGameBase.AvailableMods);
}
[Test] [Test]
public void TestFreeModSelect() public void TestFreeModSelect()
@ -42,6 +55,26 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("customisation area not expanded", () => this.ChildrenOfType<ModSettingsArea>().Single().Height == 0); AddAssert("customisation area not expanded", () => this.ChildrenOfType<ModSettingsArea>().Single().Height == 0);
} }
[Test]
public void TestSelectDeselectAll()
{
createFreeModSelect();
AddStep("click select all button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().First());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
AddStep("click deselect all button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("all mods deselected", () => !freeModSelectScreen.SelectedMods.Value.Any());
}
private void createFreeModSelect() private void createFreeModSelect()
{ {
AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen
@ -52,5 +85,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
() => freeModSelectScreen.ChildrenOfType<ModColumn>().Any() () => freeModSelectScreen.ChildrenOfType<ModColumn>().Any()
&& freeModSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded)); && freeModSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
} }
private bool assertAllAvailableModsSelected()
{
var allAvailableMods = availableMods.Value
.SelectMany(pair => pair.Value)
.Where(mod => mod.UserPlayable && mod.HasImplementation)
.ToList();
foreach (var availableMod in allAvailableMods)
{
if (freeModSelectScreen.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType()))
return false;
}
return true;
}
} }
} }

View File

@ -415,6 +415,23 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value); AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value);
} }
[Test]
public void TestDeselectAllViaButton()
{
createScreen();
changeRuleset(0);
AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() });
AddAssert("DT + HD selected", () => modSelectScreen.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
AddStep("click deselect all button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any());
}
private void waitForColumnLoad() => AddUntilStep("all column content loaded", private void waitForColumnLoad() => AddUntilStep("all column content loaded",
() => modSelectScreen.ChildrenOfType<ModColumn>().Any() && modSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded)); () => modSelectScreen.ChildrenOfType<ModColumn>().Any() && modSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));

View File

@ -275,30 +275,25 @@ namespace osu.Game.Overlays.Mods
return; return;
localAvailableMods = newMods; localAvailableMods = newMods;
Scheduler.AddOnce(loadPanels);
if (!IsLoaded)
// if we're coming from BDL, perform the first load synchronously to make sure everything is in place as early as possible.
onPanelsLoaded(createPanels());
else
asyncLoadPanels();
} }
private CancellationTokenSource? cancellationTokenSource; private CancellationTokenSource? cancellationTokenSource;
private void loadPanels() private void asyncLoadPanels()
{ {
cancellationTokenSource?.Cancel(); cancellationTokenSource?.Cancel();
var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0))); var panels = createPanels();
Task? loadTask; Task? loadTask;
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => latestLoadTask = loadTask = LoadComponentsAsync(panels, onPanelsLoaded, (cancellationTokenSource = new CancellationTokenSource()).Token);
{
panelFlow.ChildrenEnumerable = loaded;
updateState();
foreach (var panel in panelFlow)
{
panel.Active.BindValueChanged(_ => panelStateChanged(panel));
}
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
loadTask.ContinueWith(_ => loadTask.ContinueWith(_ =>
{ {
if (loadTask == latestLoadTask) if (loadTask == latestLoadTask)
@ -306,6 +301,24 @@ 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) foreach (var panel in panelFlow)
@ -386,7 +399,7 @@ namespace osu.Game.Overlays.Mods
private readonly Queue<Action> pendingSelectionOperations = new Queue<Action>(); private readonly Queue<Action> pendingSelectionOperations = new Queue<Action>();
protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; internal bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0;
protected override void Update() protected override void Update()
{ {

View File

@ -47,11 +47,6 @@ namespace osu.Game.Overlays.Mods
} }
} }
/// <summary>
/// Whether configurable <see cref="Mod"/>s can be configured by the local user.
/// </summary>
protected virtual bool AllowCustomisation => true;
/// <summary> /// <summary>
/// Whether the total score multiplier calculated from the current selected set of mods should be shown. /// Whether the total score multiplier calculated from the current selected set of mods should be shown.
/// </summary> /// </summary>
@ -59,12 +54,28 @@ namespace osu.Game.Overlays.Mods
protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys); protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys);
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() => new[]
{
customisationButton = new ShearedToggleButton(200)
{
Text = "Mod Customisation",
Active = { BindTarget = customisationVisible }
},
new ShearedButton(200)
{
Text = "Deselect All",
Action = DeselectAll
}
};
private readonly BindableBool customisationVisible = new BindableBool(); private readonly BindableBool customisationVisible = new BindableBool();
private DifficultyMultiplierDisplay? multiplierDisplay; private DifficultyMultiplierDisplay? multiplierDisplay;
private ModSettingsArea modSettingsArea = null!; private ModSettingsArea modSettingsArea = null!;
private ColumnScrollContainer columnScroll = null!; private ColumnScrollContainer columnScroll = null!;
private ColumnFlowContainer columnFlow = null!; private ColumnFlowContainer columnFlow = null!;
private ShearedToggleButton? customisationButton;
private FillFlowContainer<ShearedButton> footerButtonFlow = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
@ -95,6 +106,7 @@ namespace osu.Game.Overlays.Mods
Padding = new MarginPadding Padding = new MarginPadding
{ {
Top = (ShowTotalMultiplier ? DifficultyMultiplierDisplay.HEIGHT : 0) + PADDING, Top = (ShowTotalMultiplier ? DifficultyMultiplierDisplay.HEIGHT : 0) + PADDING,
Bottom = PADDING
}, },
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
@ -145,17 +157,21 @@ namespace osu.Game.Overlays.Mods
}); });
} }
if (AllowCustomisation) FooterContent.Child = footerButtonFlow = new FillFlowContainer<ShearedButton>
{ {
Footer.Add(new ShearedToggleButton(200) RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Padding = new MarginPadding
{ {
Anchor = Anchor.BottomLeft, Vertical = PADDING,
Origin = Anchor.BottomLeft, Horizontal = 70
Margin = new MarginPadding { Vertical = PADDING, Left = 70 }, },
Text = "Mod Customisation", Spacing = new Vector2(10),
Active = { BindTarget = customisationVisible } ChildrenEnumerable = CreateFooterButtons()
}); };
}
} }
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
@ -210,7 +226,7 @@ namespace osu.Game.Overlays.Mods
private void updateCustomisation(ValueChangedEvent<IReadOnlyList<Mod>> valueChangedEvent) private void updateCustomisation(ValueChangedEvent<IReadOnlyList<Mod>> valueChangedEvent)
{ {
if (!AllowCustomisation) if (customisationButton == null)
return; return;
bool anyCustomisableMod = false; bool anyCustomisableMod = false;
@ -244,6 +260,12 @@ namespace osu.Game.Overlays.Mods
MainAreaContent.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic); MainAreaContent.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic);
foreach (var button in footerButtonFlow)
{
if (button != customisationButton)
button.Enabled.Value = !customisationVisible.Value;
}
float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0; float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0;
modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic); modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic);
@ -319,6 +341,18 @@ namespace osu.Game.Overlays.Mods
} }
} }
protected void SelectAll()
{
foreach (var column in columnFlow.Columns)
column.SelectAll();
}
protected void DeselectAll()
{
foreach (var column in columnFlow.Columns)
column.DeselectAll();
}
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Action == GlobalAction.Back && customisationVisible.Value) if (e.Action == GlobalAction.Back && customisationVisible.Value)
@ -427,6 +461,8 @@ namespace osu.Game.Overlays.Mods
FinishTransforms(); FinishTransforms();
} }
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate || Column.SelectionAnimationRunning;
private void updateDim() private void updateDim()
{ {
Colour4 targetColour; Colour4 targetColour;

View File

@ -2,6 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
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;
@ -10,7 +13,6 @@ namespace osu.Game.Screens.OnlinePlay
{ {
public class FreeModSelectScreen : ModSelectScreen public class FreeModSelectScreen : ModSelectScreen
{ {
protected override bool AllowCustomisation => false;
protected override bool ShowTotalMultiplier => false; protected override bool ShowTotalMultiplier => false;
public new Func<Mod, bool> IsValidMod public new Func<Mod, bool> IsValidMod
@ -25,5 +27,23 @@ 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() => new[]
{
new ShearedButton(200)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = "Select All",
Action = SelectAll
},
new ShearedButton(200)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = "Deselect All",
Action = DeselectAll
}
};
} }
} }