1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 18:12:56 +08:00

Encapsulate mod hotkey selection logic in strategy pattern

This commit is contained in:
Bartłomiej Dach 2022-06-21 12:49:01 +02:00
parent b7b7de115f
commit 73124d2b1f
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
9 changed files with 145 additions and 31 deletions

View File

@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30), Padding = new MarginPadding(30),
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)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -0,0 +1,22 @@
// 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 osuTK.Input;
namespace osu.Game.Overlays.Mods.Input
{
/// <summary>
/// Encapsulates strategies of handling mod hotkeys on the <see cref="ModSelectOverlay"/>.
/// </summary>
public interface IModHotkeyHandler
{
/// <summary>
/// Attempt to handle a press of the supplied <paramref name="hotkey"/> as a selection of one of the mods in <paramref name="availableMods"/>.
/// </summary>
/// <param name="hotkey">The key that was pressed by the user.</param>
/// <param name="availableMods">The list of currently available mods.</param>
/// <returns>Whether the <paramref name="hotkey"/> was handled as a mod selection/deselection.</returns>
bool HandleHotkeyPressed(Key hotkey, IEnumerable<ModState> availableMods);
}
}

View File

@ -0,0 +1,30 @@
// 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.Game.Rulesets.Mods;
namespace osu.Game.Overlays.Mods.Input
{
/// <summary>
/// Static factory class for <see cref="IModHotkeyHandler"/>s.
/// </summary>
public static class ModHotkeyHandler
{
/// <summary>
/// Creates an appropriate <see cref="IModHotkeyHandler"/> for the given <paramref name="modType"/>.
/// </summary>
public static IModHotkeyHandler Create(ModType modType)
{
switch (modType)
{
case ModType.DifficultyReduction:
case ModType.DifficultyIncrease:
case ModType.Automation:
return SequentialModHotkeyHandler.Create(modType);
default:
return new NoopModHotkeyHandler();
}
}
}
}

View File

@ -0,0 +1,17 @@
// 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 osuTK.Input;
namespace osu.Game.Overlays.Mods.Input
{
/// <summary>
/// A no-op implementation of <see cref="IModHotkeyHandler"/>.
/// Used when a column is not handling any hotkeys at all.
/// </summary>
public class NoopModHotkeyHandler : IModHotkeyHandler
{
public bool HandleHotkeyPressed(Key hotkey, IEnumerable<ModState> availableMods) => false;
}
}

View File

@ -0,0 +1,58 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Mods;
using osuTK.Input;
namespace osu.Game.Overlays.Mods.Input
{
/// <summary>
/// This implementation of <see cref="IModHotkeyHandler"/> receives a sequence of <see cref="Key"/>s,
/// and maps the sequence of keys onto the items it is provided in <see cref="HandleHotkeyPressed"/>.
/// In this case, particular mods are not bound to particular keys, the hotkeys are a byproduct of mod ordering.
/// </summary>
public class SequentialModHotkeyHandler : IModHotkeyHandler
{
public static SequentialModHotkeyHandler Create(ModType modType)
{
switch (modType)
{
case ModType.DifficultyReduction:
return new SequentialModHotkeyHandler(new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P });
case ModType.DifficultyIncrease:
return new SequentialModHotkeyHandler(new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L });
case ModType.Automation:
return new SequentialModHotkeyHandler(new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M });
default:
throw new ArgumentOutOfRangeException(nameof(modType), modType, $"Cannot create {nameof(SequentialModHotkeyHandler)} for provided mod type");
}
}
private readonly Key[] toggleKeys;
private SequentialModHotkeyHandler(Key[] keys)
{
toggleKeys = keys;
}
public bool HandleHotkeyPressed(Key hotkey, IEnumerable<ModState> availableMods)
{
int index = Array.IndexOf(toggleKeys, hotkey);
if (index < 0)
return false;
var modState = availableMods.ElementAtOrDefault(index);
if (modState == null || modState.Filtered.Value)
return false;
modState.Active.Toggle();
return true;
}
}
}

View File

@ -21,10 +21,10 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; 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.Overlays.Mods.Input;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods
protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod);
private readonly Key[]? toggleKeys; private readonly IModHotkeyHandler hotkeyHandler;
private readonly TextFlowContainer headerText; private readonly TextFlowContainer headerText;
private readonly Box headerBackground; private readonly Box headerBackground;
@ -86,10 +86,10 @@ namespace osu.Game.Overlays.Mods
private const float header_height = 42; private const float header_height = 42;
public ModColumn(ModType modType, bool allowBulkSelection, Key[]? toggleKeys = null) public ModColumn(ModType modType, bool allowBulkSelection)
{ {
ModType = modType; ModType = modType;
this.toggleKeys = toggleKeys; hotkeyHandler = ModHotkeyHandler.Create(modType);
Width = 320; Width = 320;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
@ -425,17 +425,10 @@ namespace osu.Game.Overlays.Mods
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.Repeat)
if (toggleKeys == null) return false; return false;
int index = Array.IndexOf(toggleKeys, e.Key); return hotkeyHandler.HandleHotkeyPressed(e.Key, availableMods);
if (index < 0) return false;
var modState = availableMods.ElementAtOrDefault(index);
if (modState == null || modState.Filtered.Value) return false;
modState.Active.Toggle();
return true;
} }
#endregion #endregion

View File

@ -21,7 +21,6 @@ using osu.Game.Localisation;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Utils; using osu.Game.Utils;
using osuTK; using osuTK;
using osuTK.Input;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
@ -68,7 +67,7 @@ namespace osu.Game.Overlays.Mods
/// </summary> /// </summary>
protected virtual bool AllowCustomisation => true; protected virtual bool AllowCustomisation => true;
protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys); protected virtual ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, false);
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection; protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
@ -160,9 +159,9 @@ namespace osu.Game.Overlays.Mods
Padding = new MarginPadding { Bottom = 10 }, Padding = new MarginPadding { Bottom = 10 },
Children = new[] Children = new[]
{ {
createModColumnContent(ModType.DifficultyReduction, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }), createModColumnContent(ModType.DifficultyReduction),
createModColumnContent(ModType.DifficultyIncrease, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }), createModColumnContent(ModType.DifficultyIncrease),
createModColumnContent(ModType.Automation, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }), createModColumnContent(ModType.Automation),
createModColumnContent(ModType.Conversion), createModColumnContent(ModType.Conversion),
createModColumnContent(ModType.Fun) createModColumnContent(ModType.Fun)
} }
@ -264,9 +263,9 @@ namespace osu.Game.Overlays.Mods
column.DeselectAll(); column.DeselectAll();
} }
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) private ColumnDimContainer createModColumnContent(ModType modType)
{ {
var column = CreateModColumn(modType, toggleKeys).With(column => var column = CreateModColumn(modType).With(column =>
{ {
// 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 };

View File

@ -1,14 +1,10 @@
// 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 disable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Utils; using osu.Game.Utils;
using osuTK.Input;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
@ -19,7 +15,7 @@ namespace osu.Game.Overlays.Mods
{ {
} }
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys); protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false);
protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)
{ {
@ -44,8 +40,8 @@ namespace osu.Game.Overlays.Mods
private class UserModColumn : ModColumn private class UserModColumn : ModColumn
{ {
public UserModColumn(ModType modType, bool allowBulkSelection, [CanBeNull] Key[] toggleKeys = null) public UserModColumn(ModType modType, bool allowBulkSelection)
: base(modType, allowBulkSelection, toggleKeys) : base(modType, allowBulkSelection)
{ {
} }

View File

@ -11,7 +11,6 @@ using osu.Framework.Graphics;
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;
namespace osu.Game.Screens.OnlinePlay namespace osu.Game.Screens.OnlinePlay
{ {
@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay
IsValidMod = _ => true; IsValidMod = _ => true;
} }
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys); protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true);
protected override IEnumerable<ShearedButton> CreateFooterButtons() => base.CreateFooterButtons().Prepend( protected override IEnumerable<ShearedButton> CreateFooterButtons() => base.CreateFooterButtons().Prepend(
new SelectAllModsButton(this) new SelectAllModsButton(this)