diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index d7d073e908..bd54591b9b 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -40,6 +40,43 @@ namespace osu.Game.Tests.Visual.Settings AddWaitStep("wait for scroll", 5); } + [Test] + public void TestBindingTwoNonModifiers() + { + AddStep("press j", () => InputManager.PressKey(Key.J)); + scrollToAndStartBinding("Increase volume"); + AddStep("press k", () => InputManager.Key(Key.K)); + AddStep("release j", () => InputManager.ReleaseKey(Key.J)); + checkBinding("Increase volume", "K"); + } + + [Test] + public void TestBindingSingleKey() + { + scrollToAndStartBinding("Increase volume"); + AddStep("press k", () => InputManager.Key(Key.K)); + checkBinding("Increase volume", "K"); + } + + [Test] + public void TestBindingSingleModifier() + { + scrollToAndStartBinding("Increase volume"); + AddStep("press shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + checkBinding("Increase volume", "LShift"); + } + + [Test] + public void TestBindingSingleKeyWithModifier() + { + scrollToAndStartBinding("Increase volume"); + AddStep("press shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("press k", () => InputManager.Key(Key.K)); + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + checkBinding("Increase volume", "LShift-K"); + } + [Test] public void TestBindingMouseWheelToNonGameplay() { @@ -169,7 +206,8 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", + () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0))); } [Test] @@ -198,7 +236,8 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", + () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0))); } [Test] @@ -256,8 +295,8 @@ namespace osu.Game.Tests.Visual.Settings var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == name)); var firstButton = firstRow.ChildrenOfType().First(); - return firstButton.Text.Text == keyName; - }); + return firstButton.Text.Text.ToString(); + }, () => Is.EqualTo(keyName)); } private void scrollToAndStartBinding(string name) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index c91a6a48d4..25ab8cfad2 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -33,6 +33,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input { public class KeyBindingRow : Container, IFilterable { + /// + /// Invoked when the binding of this row is updated with a change being written. + /// + public Action BindingUpdated { get; set; } + private readonly object action; private readonly IEnumerable bindings; @@ -153,7 +158,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input Spacing = new Vector2(5), Children = new Drawable[] { - new CancelButton { Action = finalise }, + new CancelButton { Action = () => finalise(false) }, new ClearButton { Action = clear }, }, }, @@ -226,7 +231,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromMouseButton(e.Button)); return true; } @@ -240,7 +245,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } if (bindTarget.IsHovered) - finalise(); + finalise(false); // prevent updating bind target before clear button's action else if (!cancelAndClearButtons.Any(b => b.IsHovered)) updateBindTarget(); @@ -252,7 +257,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { if (bindTarget.IsHovered) { - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta), KeyCombination.FromScrollDelta(e.ScrollDelta).First()); finalise(); return true; } @@ -263,10 +268,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input protected override bool OnKeyDown(KeyDownEvent e) { - if (!HasFocus) + if (!HasFocus || e.Repeat) return false; - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromKey(e.Key)); if (!isModifier(e.Key)) finalise(); return true; @@ -288,7 +293,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (!HasFocus) return false; - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromJoystickButton(e.Button)); finalise(); return true; @@ -310,7 +315,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (!HasFocus) return false; - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromMidiKey(e.Key)); finalise(); return true; @@ -332,7 +337,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (!HasFocus) return false; - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromTabletAuxiliaryButton(e.Button)); finalise(); return true; @@ -354,7 +359,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (!HasFocus) return false; - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromTabletPenButton(e.Button)); finalise(); return true; @@ -377,10 +382,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input return; bindTarget.UpdateKeyCombination(InputKey.None); - finalise(); + finalise(false); } - private void finalise() + private void finalise(bool hasChanged = true) { if (bindTarget != null) { @@ -393,6 +398,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) bindTarget = null; + if (hasChanged) + BindingUpdated?.Invoke(this); }); } @@ -417,7 +424,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input protected override void OnFocusLost(FocusLostEvent e) { - finalise(); + finalise(false); base.OnFocusLost(e); } @@ -563,6 +570,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } + /// + /// Update from a key combination, only allowing a single non-modifier key to be specified. + /// + /// A generated from the full input state. + /// The key which triggered this update, and should be used as the binding. + public void UpdateKeyCombination(KeyCombination fullState, InputKey triggerKey) => + UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey))); + public void UpdateKeyCombination(KeyCombination newCombination) { if (KeyBinding.RulesetName != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination)) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 628fe08607..98d569948f 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -19,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input { public abstract class KeyBindingsSubsection : SettingsSubsection { + /// + /// After a successful binding, automatically select the next binding row to make quickly + /// binding a large set of keys easier on the user. + /// + protected virtual bool AutoAdvanceTarget => false; + protected IEnumerable Defaults; public RulesetInfo Ruleset { get; protected set; } @@ -49,7 +55,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.ActionInt.Equals(intKey)).ToList()) { AllowMainMouseButtons = Ruleset != null, - Defaults = defaultGroup.Select(d => d.KeyCombination) + Defaults = defaultGroup.Select(d => d.KeyCombination), + BindingUpdated = onBindingUpdated }); } @@ -58,6 +65,16 @@ namespace osu.Game.Overlays.Settings.Sections.Input Action = () => Children.OfType().ForEach(k => k.RestoreDefaults()) }); } + + private void onBindingUpdated(KeyBindingRow sender) + { + if (AutoAdvanceTarget) + { + var next = Children.SkipWhile(c => c != sender).Skip(1).FirstOrDefault(); + if (next != null) + GetContainingInputManager().ChangeFocus(next); + } + } } public class ResetButton : DangerousSettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Input/VariantBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/VariantBindingsSubsection.cs index a0f069b3bb..a6f6c28463 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/VariantBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/VariantBindingsSubsection.cs @@ -8,6 +8,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { public class VariantBindingsSubsection : KeyBindingsSubsection { + protected override bool AutoAdvanceTarget => true; + protected override LocalisableString Header { get; } public VariantBindingsSubsection(RulesetInfo ruleset, int variant) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 974e240552..20ef128ee9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -436,8 +436,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.OnDragEnd(e); - OnDragHandled?.Invoke(null); + dragOperation?.Cancel(); + dragOperation = null; + changeHandler?.EndChange(); + OnDragHandled?.Invoke(null); } }