diff --git a/osu.Game/Graphics/Carousel/Carousel.ScrollContainer.cs b/osu.Game/Graphics/Carousel/Carousel.ScrollContainer.cs
index 5f1488513a..625c246c4e 100644
--- a/osu.Game/Graphics/Carousel/Carousel.ScrollContainer.cs
+++ b/osu.Game/Graphics/Carousel/Carousel.ScrollContainer.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
@@ -31,10 +32,12 @@ namespace osu.Game.Graphics.Carousel
/// Implementation of scroll container which handles very large vertical lists by internally using double precision
/// for pre-display Y values.
///
- protected partial class ScrollContainer : UserTrackingScrollContainer, IKeyBindingHandler
+ protected partial class ScrollContainer : UserTrackingScrollContainer, IKeyBindingHandler, IKeyBindingHandler
{
public Action? OnPageUp { get; init; }
public Action? OnPageDown { get; init; }
+ public Action? OnListStart { get; init; }
+ public Action? OnListEnd { get; init; }
public readonly Container Panels;
@@ -146,6 +149,25 @@ namespace osu.Game.Graphics.Carousel
return base.OnKeyDown(e);
}
+ public new bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (IsHandlingKeyboardScrolling)
+ {
+ switch (e.Action)
+ {
+ case PlatformAction.MoveBackwardLine:
+ OnListStart?.Invoke();
+ return true;
+
+ case PlatformAction.MoveForwardLine:
+ OnListEnd?.Invoke();
+ return true;
+ }
+ }
+
+ return base.OnPressed(e);
+ }
+
public bool OnPressed(KeyBindingPressEvent e)
{
switch (e.Action)
diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs
index 13d3c22c19..5164068d53 100644
--- a/osu.Game/Graphics/Carousel/Carousel.cs
+++ b/osu.Game/Graphics/Carousel/Carousel.cs
@@ -319,6 +319,8 @@ namespace osu.Game.Graphics.Carousel
RelativeSizeAxes = Axes.Both,
OnPageUp = () => Scheduler.AddOnce(traverseFromKey, new TraversalOperation(TraversalType.Page, -1)),
OnPageDown = () => Scheduler.AddOnce(traverseFromKey, new TraversalOperation(TraversalType.Page, 1)),
+ OnListStart = () => Scheduler.AddOnce(traverseFromKey, new TraversalOperation(TraversalType.Edge, -1)),
+ OnListEnd = () => Scheduler.AddOnce(traverseFromKey, new TraversalOperation(TraversalType.Edge, 1)),
};
Items.BindCollectionChanged((_, args) =>
@@ -554,6 +556,10 @@ namespace osu.Game.Graphics.Carousel
traverseKeyboardPage(traversal.Direction);
break;
+ case TraversalType.Edge:
+ traverseKeyboardEdge(traversal.Direction);
+ break;
+
case TraversalType.Set:
traverseSetSelection(traversal.Direction);
break;
@@ -572,6 +578,7 @@ namespace osu.Game.Graphics.Carousel
Keyboard,
Set,
Page,
+ Edge,
Group
}
@@ -682,6 +689,27 @@ namespace osu.Game.Graphics.Carousel
}
}
+ ///
+ /// Select the first or last item in the carousel.
+ ///
+ /// Positive for last item, negative for first item.
+ private void traverseKeyboardEdge(int direction)
+ {
+ if (carouselItems == null || carouselItems.Count == 0)
+ return;
+
+ var item = direction > 0
+ ? carouselItems.LastOrDefault(x => x.IsVisible)
+ : carouselItems.FirstOrDefault(x => x.IsVisible);
+
+ if (item != null && !CheckModelEquality(item.Model, currentKeyboardSelection.Model))
+ {
+ setKeyboardSelection(item.Model);
+ ScrollToSelection();
+ playTraversalSound();
+ }
+ }
+
///
/// Select the next valid group selection relative to a current selection.
/// This is generally for keyboard based traversal.