mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 03:22:55 +08:00
Merge pull request #18796 from bdach/mod-overlay/legacy-key-bindings
Add setting option to toggle between mod overlay hotkey styles
This commit is contained in:
commit
b660119de7
@ -9,9 +9,11 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Mods.Input;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Utils;
|
||||
@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
[TestCase(ModType.DifficultyReduction)]
|
||||
[TestCase(ModType.DifficultyIncrease)]
|
||||
[TestCase(ModType.Conversion)]
|
||||
@ -132,14 +137,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyboardSelection()
|
||||
public void TestSequentialKeyboardSelection()
|
||||
{
|
||||
AddStep("set sequential hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential));
|
||||
|
||||
ModColumn column = null!;
|
||||
AddStep("create content", () => Child = new Container
|
||||
{
|
||||
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,
|
||||
@ -158,9 +165,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("set filter to NF", () => setFilter(mod => mod.Acronym == "NF"));
|
||||
|
||||
AddStep("press W", () => InputManager.Key(Key.W));
|
||||
AddAssert("NF panel not selected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
||||
|
||||
AddStep("press Q", () => InputManager.Key(Key.Q));
|
||||
AddAssert("NF panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
||||
|
||||
AddStep("press W again", () => InputManager.Key(Key.W));
|
||||
AddStep("press Q again", () => InputManager.Key(Key.Q));
|
||||
AddAssert("NF panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
|
||||
|
||||
AddStep("filter out everything", () => setFilter(_ => false));
|
||||
@ -171,6 +181,113 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("clear filter", () => setFilter(null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClassicKeyboardExclusiveSelection()
|
||||
{
|
||||
AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic));
|
||||
|
||||
ModColumn column = null!;
|
||||
AddStep("create content", () => Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(30),
|
||||
Child = column = new ModColumn(ModType.DifficultyIncrease, false)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AvailableMods = getExampleModsFor(ModType.DifficultyIncrease)
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded);
|
||||
|
||||
AddStep("press A", () => InputManager.Key(Key.A));
|
||||
AddAssert("HR panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "HR").Active.Value);
|
||||
|
||||
AddStep("press A again", () => InputManager.Key(Key.A));
|
||||
AddAssert("HR panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "HR").Active.Value);
|
||||
|
||||
AddStep("press D", () => InputManager.Key(Key.D));
|
||||
AddAssert("DT panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
|
||||
|
||||
AddStep("press D again", () => InputManager.Key(Key.D));
|
||||
AddAssert("DT panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
|
||||
AddAssert("NC panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
|
||||
|
||||
AddStep("press D again", () => InputManager.Key(Key.D));
|
||||
AddAssert("DT panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
|
||||
AddAssert("NC panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
|
||||
|
||||
AddStep("press Shift-D", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Key(Key.D);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
AddAssert("DT panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
|
||||
AddAssert("NC panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
|
||||
|
||||
AddStep("press J", () => InputManager.Key(Key.J));
|
||||
AddAssert("no change", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Active.Value).Mod.Acronym == "NC");
|
||||
|
||||
AddStep("filter everything but NC", () => setFilter(mod => mod.Acronym == "NC"));
|
||||
|
||||
AddStep("press A", () => InputManager.Key(Key.A));
|
||||
AddAssert("no change", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Active.Value).Mod.Acronym == "NC");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClassicKeyboardIncompatibleSelection()
|
||||
{
|
||||
AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic));
|
||||
|
||||
ModColumn column = null!;
|
||||
AddStep("create content", () => Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(30),
|
||||
Child = column = new ModColumn(ModType.DifficultyIncrease, true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AvailableMods = getExampleModsFor(ModType.DifficultyIncrease)
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded);
|
||||
|
||||
AddStep("press A", () => InputManager.Key(Key.A));
|
||||
AddAssert("HR panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "HR").Active.Value);
|
||||
|
||||
AddStep("press A again", () => InputManager.Key(Key.A));
|
||||
AddAssert("HR panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "HR").Active.Value);
|
||||
|
||||
AddStep("press D", () => InputManager.Key(Key.D));
|
||||
AddAssert("DT panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
|
||||
AddAssert("NC panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
|
||||
|
||||
AddStep("press D again", () => InputManager.Key(Key.D));
|
||||
AddAssert("DT panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
|
||||
AddAssert("NC panel deselected", () => !this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
|
||||
|
||||
AddStep("press Shift-D", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Key(Key.D);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
AddAssert("DT panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
|
||||
AddAssert("NC panel selected", () => this.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
|
||||
|
||||
AddStep("press J", () => InputManager.Key(Key.J));
|
||||
AddAssert("no change", () => this.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
|
||||
|
||||
AddStep("filter everything but NC", () => setFilter(mod => mod.Acronym == "NC"));
|
||||
|
||||
AddStep("press A", () => InputManager.Key(Key.A));
|
||||
AddAssert("no change", () => this.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
|
||||
}
|
||||
|
||||
private void setFilter(Func<Mod, bool>? filter)
|
||||
{
|
||||
foreach (var modState in this.ChildrenOfType<ModColumn>().Single().AvailableMods)
|
||||
@ -181,8 +298,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public new bool SelectionAnimationRunning => base.SelectionAnimationRunning;
|
||||
|
||||
public TestModColumn(ModType modType, bool allowBulkSelection)
|
||||
: base(modType, allowBulkSelection)
|
||||
public TestModColumn(ModType modType, bool allowIncompatibleSelection)
|
||||
: base(modType, allowIncompatibleSelection)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods.Input;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
@ -47,6 +48,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title);
|
||||
|
||||
SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
|
||||
SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential);
|
||||
|
||||
SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
|
||||
|
||||
@ -324,6 +326,7 @@ namespace osu.Game.Configuration
|
||||
SongSelectGroupingMode,
|
||||
SongSelectSortingMode,
|
||||
RandomSelectAlgorithm,
|
||||
ModSelectHotkeyStyle,
|
||||
ShowFpsDisplay,
|
||||
ChatDisplayHeight,
|
||||
BeatmapListingCardSize,
|
||||
|
@ -106,6 +106,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString RandomSelectionAlgorithm => new TranslatableString(getKey(@"random_selection_algorithm"), @"Random selection algorithm");
|
||||
|
||||
/// <summary>
|
||||
/// "Mod select hotkey style"
|
||||
/// </summary>
|
||||
public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style");
|
||||
|
||||
/// <summary>
|
||||
/// "no limit"
|
||||
/// </summary>
|
||||
|
98
osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs
Normal file
98
osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs
Normal file
@ -0,0 +1,98 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Mods.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses bindings from stable 1:1.
|
||||
/// </summary>
|
||||
public class ClassicModHotkeyHandler : IModHotkeyHandler
|
||||
{
|
||||
private static readonly Dictionary<Key, Type[]> mod_type_lookup = new Dictionary<Key, Type[]>
|
||||
{
|
||||
[Key.Q] = new[] { typeof(ModEasy) },
|
||||
[Key.W] = new[] { typeof(ModNoFail) },
|
||||
[Key.E] = new[] { typeof(ModHalfTime) },
|
||||
[Key.A] = new[] { typeof(ModHardRock) },
|
||||
[Key.S] = new[] { typeof(ModSuddenDeath), typeof(ModPerfect) },
|
||||
[Key.D] = new[] { typeof(ModDoubleTime), typeof(ModNightcore) },
|
||||
[Key.F] = new[] { typeof(ModHidden) },
|
||||
[Key.G] = new[] { typeof(ModFlashlight) },
|
||||
[Key.Z] = new[] { typeof(ModRelax) },
|
||||
[Key.V] = new[] { typeof(ModAutoplay), typeof(ModCinema) }
|
||||
};
|
||||
|
||||
private readonly bool allowIncompatibleSelection;
|
||||
|
||||
public ClassicModHotkeyHandler(bool allowIncompatibleSelection)
|
||||
{
|
||||
this.allowIncompatibleSelection = allowIncompatibleSelection;
|
||||
}
|
||||
|
||||
public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable<ModState> availableMods)
|
||||
{
|
||||
if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch))
|
||||
return false;
|
||||
|
||||
var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && !modState.Filtered.Value).ToArray();
|
||||
|
||||
if (matchingMods.Length == 0)
|
||||
return false;
|
||||
|
||||
if (matchingMods.Length == 1)
|
||||
{
|
||||
matchingMods.Single().Active.Toggle();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowIncompatibleSelection)
|
||||
{
|
||||
// easier path - multiple incompatible mods can be active at a time.
|
||||
// this is used in the free mod select overlay.
|
||||
// in this case, just toggle everything.
|
||||
bool anyActive = matchingMods.Any(mod => mod.Active.Value);
|
||||
foreach (var mod in matchingMods)
|
||||
mod.Active.Value = !anyActive;
|
||||
return true;
|
||||
}
|
||||
|
||||
// we now know there are multiple possible mods to handle, and only one of them can be active at a time.
|
||||
// let's make sure of this just in case.
|
||||
Debug.Assert(matchingMods.Count(modState => modState.Active.Value) <= 1);
|
||||
int currentSelectedIndex = Array.FindIndex(matchingMods, modState => modState.Active.Value);
|
||||
|
||||
// `FindIndex` will return -1 if it doesn't find the item.
|
||||
// this is convenient in the forward direction, since if we add 1 then we'll end up at the first item,
|
||||
// but less so in the backwards direction.
|
||||
// for convenience, detect this situation and set the index to one index past the last item.
|
||||
// this makes it so that if we subtract 1 then we'll end up at the last item again.
|
||||
if (currentSelectedIndex < 0 && e.ShiftPressed)
|
||||
currentSelectedIndex = matchingMods.Length;
|
||||
|
||||
int indexToSelect = e.ShiftPressed ? currentSelectedIndex - 1 : currentSelectedIndex + 1;
|
||||
|
||||
// `currentSelectedIndex` and `indexToSelect` can both be equal to -1 or `matchingMods.Length`.
|
||||
// if the former is beyond array range, it means nothing was previously selected and so there's nothing to deselect.
|
||||
// if the latter is beyond array range, it means that either the previous selection was first and we're going backwards,
|
||||
// or it was last and we're going forwards.
|
||||
// in either case there is nothing to select.
|
||||
if (currentSelectedIndex >= 0 && currentSelectedIndex <= matchingMods.Length - 1)
|
||||
matchingMods[currentSelectedIndex].Active.Value = false;
|
||||
if (indexToSelect >= 0 && indexToSelect <= matchingMods.Length - 1)
|
||||
matchingMods[indexToSelect].Active.Value = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool matches(ModState modState, Type[] typesToMatch)
|
||||
=> typesToMatch.Any(typeToMatch => typeToMatch.IsInstanceOfType(modState.Mod));
|
||||
}
|
||||
}
|
22
osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs
Normal file
22
osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs
Normal 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 osu.Framework.Input.Events;
|
||||
|
||||
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 the supplied <see cref="KeyDownEvent"/> as a selection of one of the mods in <paramref name="availableMods"/>.
|
||||
/// </summary>
|
||||
/// <param name="e">The event representing the user's keypress.</param>
|
||||
/// <param name="availableMods">The list of currently available mods.</param>
|
||||
/// <returns>Whether the supplied event was handled as a mod selection/deselection.</returns>
|
||||
bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable<ModState> availableMods);
|
||||
}
|
||||
}
|
27
osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.cs
Normal file
27
osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// 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>
|
||||
/// The style of hotkey handling to use on the mod select screen.
|
||||
/// </summary>
|
||||
public enum ModSelectHotkeyStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Each letter row on the keyboard controls one of the three first <see cref="ModColumn"/>s.
|
||||
/// Individual letters in a row trigger the mods in a sequential fashion.
|
||||
/// Uses <see cref="SequentialModHotkeyHandler"/>.
|
||||
/// </summary>
|
||||
Sequential,
|
||||
|
||||
/// <summary>
|
||||
/// Matches keybindings from stable 1:1.
|
||||
/// One keybinding can toggle between what used to be <see cref="MultiMod"/>s on stable,
|
||||
/// and some mods in a column may not have any hotkeys at all.
|
||||
/// </summary>
|
||||
Classic
|
||||
}
|
||||
}
|
17
osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs
Normal file
17
osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs
Normal 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 osu.Framework.Input.Events;
|
||||
|
||||
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(KeyDownEvent e, IEnumerable<ModState> availableMods) => false;
|
||||
}
|
||||
}
|
59
osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs
Normal file
59
osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs
Normal file
@ -0,0 +1,59 @@
|
||||
// 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.Framework.Input.Events;
|
||||
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(KeyDownEvent e, IEnumerable<ModState> availableMods)
|
||||
{
|
||||
int index = Array.IndexOf(toggleKeys, e.Key);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
var modState = availableMods.Where(modState => !modState.Filtered.Value).ElementAtOrDefault(index);
|
||||
if (modState == null)
|
||||
return false;
|
||||
|
||||
modState.Active.Toggle();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,14 +17,15 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
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 +71,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod);
|
||||
|
||||
private readonly Key[]? toggleKeys;
|
||||
private readonly bool allowIncompatibleSelection;
|
||||
|
||||
private readonly TextFlowContainer headerText;
|
||||
private readonly Box headerBackground;
|
||||
@ -81,15 +82,18 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private Colour4 accentColour;
|
||||
|
||||
private Bindable<ModSelectHotkeyStyle> hotkeyStyle = null!;
|
||||
private IModHotkeyHandler hotkeyHandler = null!;
|
||||
|
||||
private Task? latestLoadTask;
|
||||
internal bool ItemsLoaded => latestLoadTask == null;
|
||||
|
||||
private const float header_height = 42;
|
||||
|
||||
public ModColumn(ModType modType, bool allowBulkSelection, Key[]? toggleKeys = null)
|
||||
public ModColumn(ModType modType, bool allowIncompatibleSelection)
|
||||
{
|
||||
ModType = modType;
|
||||
this.toggleKeys = toggleKeys;
|
||||
this.allowIncompatibleSelection = allowIncompatibleSelection;
|
||||
|
||||
Width = 320;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
@ -197,7 +201,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
createHeaderText();
|
||||
|
||||
if (allowBulkSelection)
|
||||
if (allowIncompatibleSelection)
|
||||
{
|
||||
controlContainer.Height = 35;
|
||||
controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this)
|
||||
@ -231,7 +235,7 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
||||
private void load(OverlayColourProvider colourProvider, OsuColour colours, OsuConfigManager configManager)
|
||||
{
|
||||
headerBackground.Colour = accentColour = colours.ForModType(ModType);
|
||||
|
||||
@ -243,6 +247,8 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3);
|
||||
contentBackground.Colour = colourProvider.Background4;
|
||||
|
||||
hotkeyStyle = configManager.GetBindable<ModSelectHotkeyStyle>(OsuSetting.ModSelectHotkeyStyle);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -250,6 +256,7 @@ namespace osu.Game.Overlays.Mods
|
||||
base.LoadComplete();
|
||||
|
||||
toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true);
|
||||
hotkeyStyle.BindValueChanged(val => hotkeyHandler = createHotkeyHandler(val.NewValue), true);
|
||||
asyncLoadPanels();
|
||||
}
|
||||
|
||||
@ -423,19 +430,32 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
#region Keyboard selection support
|
||||
|
||||
/// <summary>
|
||||
/// Creates an appropriate <see cref="IModHotkeyHandler"/> for this column's <see cref="ModType"/> and
|
||||
/// the supplied <paramref name="hotkeyStyle"/>.
|
||||
/// </summary>
|
||||
private IModHotkeyHandler createHotkeyHandler(ModSelectHotkeyStyle hotkeyStyle)
|
||||
{
|
||||
switch (ModType)
|
||||
{
|
||||
case ModType.DifficultyReduction:
|
||||
case ModType.DifficultyIncrease:
|
||||
case ModType.Automation:
|
||||
return hotkeyStyle == ModSelectHotkeyStyle.Sequential
|
||||
? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(ModType)
|
||||
: new ClassicModHotkeyHandler(allowIncompatibleSelection);
|
||||
|
||||
default:
|
||||
return new NoopModHotkeyHandler();
|
||||
}
|
||||
}
|
||||
|
||||
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, availableMods);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -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 };
|
||||
|
@ -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 allowIncompatibleSelection)
|
||||
: base(modType, allowIncompatibleSelection)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Mods.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
@ -61,6 +62,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
LabelText = UserInterfaceStrings.RandomSelectionAlgorithm,
|
||||
Current = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm),
|
||||
},
|
||||
new SettingsEnumDropdown<ModSelectHotkeyStyle>
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ModSelectHotkeyStyle,
|
||||
Current = config.GetBindable<ModSelectHotkeyStyle>(OsuSetting.ModSelectHotkeyStyle),
|
||||
ClassicDefault = ModSelectHotkeyStyle.Classic
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user