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()
{
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index e174c46610..6f913a3177 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;
@@ -301,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);
@@ -452,32 +456,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 +507,44 @@ namespace osu.Game.Screens.Select
public void OnReleased(GlobalAction action)
{
+ switch (action)
+ {
+ case GlobalAction.SelectNext:
+ case GlobalAction.SelectPrevious:
+ endRepeatSelection(action);
+ break;
+ }
}
+ 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;
+ repeatDelegate = this.BeginKeyRepeat(Scheduler, action);
+ }
+
+ private void endRepeatSelection(object source = null)
+ {
+ // only the most recent source should be able to cancel the current action.
+ if (source != null && !EqualityComparer