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

View File

@ -21,7 +21,6 @@ using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;
using osuTK;
using osuTK.Input;
namespace osu.Game.Overlays.Mods
{
@ -68,7 +67,7 @@ namespace osu.Game.Overlays.Mods
/// </summary>
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;
@ -160,9 +159,9 @@ namespace osu.Game.Overlays.Mods
Padding = new MarginPadding { Bottom = 10 },
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.DifficultyIncrease, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }),
createModColumnContent(ModType.Automation, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }),
createModColumnContent(ModType.DifficultyReduction),
createModColumnContent(ModType.DifficultyIncrease),
createModColumnContent(ModType.Automation),
createModColumnContent(ModType.Conversion),
createModColumnContent(ModType.Fun)
}
@ -264,9 +263,9 @@ namespace osu.Game.Overlays.Mods
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.
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.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;
using osuTK.Input;
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)
{
@ -44,8 +40,8 @@ namespace osu.Game.Overlays.Mods
private class UserModColumn : ModColumn
{
public UserModColumn(ModType modType, bool allowBulkSelection, [CanBeNull] Key[] toggleKeys = null)
: base(modType, allowBulkSelection, toggleKeys)
public UserModColumn(ModType modType, bool allowBulkSelection)
: base(modType, allowBulkSelection)
{
}

View File

@ -11,7 +11,6 @@ using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osuTK.Input;
namespace osu.Game.Screens.OnlinePlay
{
@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay
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(
new SelectAllModsButton(this)