From d7742766d054ca1d036985b6ca6c62ab946851c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 19:47:23 +0900 Subject: [PATCH 1/7] Add key/press repeat support to carousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 63 ++++++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e174c46610..6611955cce 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -452,32 +452,49 @@ namespace osu.Game.Screens.Select /// public void ScrollToSelected() => scrollPositionCache.Invalidate(); + #region Key / button selection logic + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) { case Key.Left: - SelectNext(-1, true); + if (!e.Repeat) + beginRepeatSelection(() => SelectNext(-1, true), e.Key); return true; case Key.Right: - SelectNext(1, true); + if (!e.Repeat) + beginRepeatSelection(() => SelectNext(1, true), e.Key); return true; } return false; } + protected override void OnKeyUp(KeyUpEvent e) + { + switch (e.Key) + { + case Key.Left: + case Key.Right: + endRepeatSelection(e.Key); + break; + } + + base.OnKeyUp(e); + } + public bool OnPressed(GlobalAction action) { switch (action) { case GlobalAction.SelectNext: - SelectNext(1, false); + beginRepeatSelection(() => SelectNext(1, false), action); return true; case GlobalAction.SelectPrevious: - SelectNext(-1, false); + beginRepeatSelection(() => SelectNext(-1, false), action); return true; } @@ -486,8 +503,46 @@ namespace osu.Game.Screens.Select public void OnReleased(GlobalAction action) { + switch (action) + { + case GlobalAction.SelectNext: + case GlobalAction.SelectPrevious: + endRepeatSelection(action); + break; + } } + private const double repeat_interval = 120; + + private ScheduledDelegate repeatDelegate; + private object lastRepeatSource; + + /// + /// Begin repeating the specified selection action. + /// + /// The action to perform. + /// The source of the action. Used in conjunction with to only cancel the correct action (most recently pressed key). + private void beginRepeatSelection(Action action, object source) + { + endRepeatSelection(); + + lastRepeatSource = source; + Scheduler.Add(repeatDelegate = new ScheduledDelegate(action, Time.Current, repeat_interval)); + } + + private void endRepeatSelection(object source = null) + { + // only the most recent source should be able to cancel the current action. + if (source != null && !EqualityComparer.Default.Equals(lastRepeatSource, source)) + return; + + repeatDelegate?.Cancel(); + repeatDelegate = null; + lastRepeatSource = null; + } + + #endregion + protected override void Update() { base.Update(); From c36d9d4fc3fa87aef05600dae76ada50bf5c076d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 20:01:29 +0900 Subject: [PATCH 2/7] Add test coverage --- .../SongSelect/TestSceneBeatmapCarousel.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 2f12194ede..073d75692e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -17,11 +17,12 @@ using osu.Game.Rulesets; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; +using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestSceneBeatmapCarousel : OsuTestScene + public class TestSceneBeatmapCarousel : OsuManualInputManagerTestScene { private TestBeatmapCarousel carousel; private RulesetStore rulesets; @@ -39,6 +40,43 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestKeyRepeat() + { + loadBeatmaps(); + advanceSelection(false); + + AddStep("press down arrow", () => InputManager.PressKey(Key.Down)); + + BeatmapInfo selection = null; + + checkSelectionIterating(true); + + AddStep("press up arrow", () => InputManager.PressKey(Key.Up)); + + checkSelectionIterating(true); + + AddStep("release down arrow", () => InputManager.ReleaseKey(Key.Down)); + + checkSelectionIterating(true); + + AddStep("release up arrow", () => InputManager.ReleaseKey(Key.Up)); + + checkSelectionIterating(false); + + void checkSelectionIterating(bool isIterating) + { + for (int i = 0; i < 3; i++) + { + AddStep("store selection", () => selection = carousel.SelectedBeatmap); + if (isIterating) + AddUntilStep("selection changed", () => carousel.SelectedBeatmap != selection); + else + AddUntilStep("selection not changed", () => carousel.SelectedBeatmap == selection); + } + } + } + [Test] public void TestRecommendedSelection() { From d704a4597dc60d882d4b8f54d6245cbca81ab67f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 21:33:02 +0900 Subject: [PATCH 3/7] Use existing helper function for key repeat --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6611955cce..ad19c9661f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -19,6 +19,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; @@ -527,7 +528,7 @@ namespace osu.Game.Screens.Select endRepeatSelection(); lastRepeatSource = source; - Scheduler.Add(repeatDelegate = new ScheduledDelegate(action, Time.Current, repeat_interval)); + Scheduler.Add(repeatDelegate = this.BeginKeyRepeat(Scheduler, action)); } private void endRepeatSelection(object source = null) From e3d654d33f2f5d1daeed1f72e263e1e943104ed3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:14:02 +0900 Subject: [PATCH 4/7] Cleanup --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ad19c9661f..c58b34f9f2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -513,8 +513,6 @@ namespace osu.Game.Screens.Select } } - private const double repeat_interval = 120; - private ScheduledDelegate repeatDelegate; private object lastRepeatSource; From 1b4c31a84f3e2e4654ea065f0dee3095bcae47c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:14:08 +0900 Subject: [PATCH 5/7] Remove double schedule --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c58b34f9f2..5fbe917943 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -526,7 +526,7 @@ namespace osu.Game.Screens.Select endRepeatSelection(); lastRepeatSource = source; - Scheduler.Add(repeatDelegate = this.BeginKeyRepeat(Scheduler, action)); + repeatDelegate = this.BeginKeyRepeat(Scheduler, action); } private void endRepeatSelection(object source = null) From 8f6d52550f6ddfb46a7f9f8384fa1bd7fbc5c34b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:32:13 +0900 Subject: [PATCH 6/7] Fix potential exception if button is pressed before selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5fbe917943..71ccd6fada 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -279,6 +279,9 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { + if (selectedBeatmap == null) + return; + if (beatmapSets.All(s => s.Filtered.Value)) return; From 099416b4c3d981d7e844ca4b57833597f8e7715a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 21:03:34 +0900 Subject: [PATCH 7/7] Move check inside next difficulty selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 71ccd6fada..6f913a3177 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -279,9 +279,6 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - if (selectedBeatmap == null) - return; - if (beatmapSets.All(s => s.Filtered.Value)) return; @@ -305,6 +302,9 @@ namespace osu.Game.Screens.Select private void selectNextDifficulty(int direction) { + if (selectedBeatmap == null) + return; + var unfilteredDifficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList(); int index = unfilteredDifficulties.IndexOf(selectedBeatmap);