1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-06 01:33:50 +08:00

Merge pull request #33279 from peppy/song-select-v2-reset-scroll

SongSelectV2: Scroll to selection when mouse moves to left area
This commit is contained in:
Bartłomiej Dach
2025-05-27 10:50:12 +02:00
committed by GitHub
Unverified
3 changed files with 108 additions and 19 deletions
@@ -22,6 +22,7 @@ using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics.Carousel;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
@@ -52,11 +53,16 @@ using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Screens.Select.Options;
using osu.Game.Screens.SelectV2;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Resources;
using osu.Game.Utils;
using osuTK;
using osuTK.Input;
using BeatmapCarousel = osu.Game.Screens.Select.BeatmapCarousel;
using CollectionDropdown = osu.Game.Collections.CollectionDropdown;
using FilterControl = osu.Game.Screens.Select.FilterControl;
using FooterButtonRandom = osu.Game.Screens.Select.FooterButtonRandom;
namespace osu.Game.Tests.Visual.Navigation
{
@@ -274,6 +280,58 @@ namespace osu.Game.Tests.Visual.Navigation
double getCarouselScrollPosition() => Game.ChildrenOfType<UserTrackingScrollContainer<DrawableCarouselItem>>().Single().Current;
}
[Test]
public void TestNewSongSelectScrollHandling()
{
SoloSongSelect songSelect = null;
double scrollPosition = 0;
AddStep("set game volume to max", () => Game.Dependencies.Get<FrameworkConfigManager>().SetValue(FrameworkSetting.VolumeUniversal, 1d));
AddUntilStep("wait for volume overlay to hide", () => Game.ChildrenOfType<VolumeOverlay>().SingleOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Hidden));
PushAndConfirm(() => songSelect = new SoloSongSelect());
AddUntilStep("wait for song select", () => songSelect.IsLoaded);
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
AddUntilStep("wait for beatmap", () => Game.ChildrenOfType<PanelBeatmapSet>().Any());
AddWaitStep("wait for scroll", 10);
AddStep("store scroll position", () => scrollPosition = getCarouselScrollPosition());
AddStep("move to title wedge", () => InputManager.MoveMouseTo(
songSelect.ChildrenOfType<BeatmapTitleWedge>().Single()));
AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1));
AddAssert("carousel didn't move", getCarouselScrollPosition, () => Is.EqualTo(scrollPosition));
AddRepeatStep("alt-scroll down", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.ScrollVerticalBy(-1);
InputManager.ReleaseKey(Key.AltLeft);
}, 5);
AddAssert("game volume decreased", () => Game.Dependencies.Get<FrameworkConfigManager>().Get<double>(FrameworkSetting.VolumeUniversal), () => Is.LessThan(1));
AddStep("set game volume to max", () => Game.Dependencies.Get<FrameworkConfigManager>().SetValue(FrameworkSetting.VolumeUniversal, 1d));
AddStep("move to metadata wedge", () => InputManager.MoveMouseTo(
songSelect.ChildrenOfType<BeatmapMetadataWedge>().Single()));
AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1));
AddAssert("carousel didn't move", getCarouselScrollPosition, () => Is.EqualTo(scrollPosition));
AddRepeatStep("alt-scroll down", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.ScrollVerticalBy(-1);
InputManager.ReleaseKey(Key.AltLeft);
}, 5);
AddAssert("game volume decreased", () => Game.Dependencies.Get<FrameworkConfigManager>().Get<double>(FrameworkSetting.VolumeUniversal), () => Is.LessThan(1));
AddStep("move to carousel", () => InputManager.MoveMouseTo(songSelect.ChildrenOfType<Screens.SelectV2.BeatmapCarousel>().Single()));
AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1));
AddAssert("carousel moved", getCarouselScrollPosition, () => Is.Not.EqualTo(scrollPosition));
double getCarouselScrollPosition() => Game.ChildrenOfType<Carousel<BeatmapInfo>>().Single().ChildrenOfType<UserTrackingScrollContainer>().Single().Current;
}
/// <summary>
/// This tests that the F1 key will open the mod select overlay, and not be handled / blocked by the music controller (which has the same default binding
/// but should be handled *after* song select).
+18 -8
View File
@@ -136,6 +136,17 @@ namespace osu.Game.Graphics.Carousel
selectionValid.Invalidate();
}
/// <summary>
/// Scroll carousel to the selected item if available.
/// </summary>
public void ScrollToSelection()
{
// TODO: this likely needs to be delayed until currentKeyboardSelection has a valid value.
// Early calls to `ScrollToSelection` will currently silently fail.
if (currentKeyboardSelection.CarouselItem != null)
Scroll.ScrollTo(currentKeyboardSelection.CarouselItem.CarouselYPosition - visibleHalfHeight + BleedTop);
}
/// <summary>
/// Returns the vertical spacing between two given carousel items. Negative value can be used to create an overlapping effect.
/// </summary>
@@ -316,7 +327,7 @@ namespace osu.Game.Graphics.Carousel
refreshAfterSelection();
if (!Scroll.UserScrolling)
scrollToSelection();
ScrollToSelection();
NewItemsPresented?.Invoke(carouselItems);
});
@@ -553,12 +564,6 @@ namespace osu.Game.Graphics.Carousel
Scroll.OffsetScrollPosition((float)(currentKeyboardSelection.YPosition!.Value - prevKeyboard.YPosition.Value));
}
private void scrollToSelection()
{
if (currentKeyboardSelection.CarouselItem != null)
Scroll.ScrollTo(currentKeyboardSelection.CarouselItem.CarouselYPosition - visibleHalfHeight + BleedTop);
}
#endregion
#region Display handling
@@ -598,7 +603,7 @@ namespace osu.Game.Graphics.Carousel
refreshAfterSelection();
// Always scroll to selection in this case (regardless of `UserScrolling` state), centering the selection.
scrollToSelection();
ScrollToSelection();
selectionValid.Validate();
}
@@ -820,6 +825,11 @@ namespace osu.Game.Graphics.Carousel
public void SetLayoutHeight(float height) => Panels.Height = height;
/// <summary>
/// Allow handling right click scroll outside of the carousel's display area.
/// </summary>
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public CarouselScrollContainer()
{
// Managing our own custom layout within ScrollContent causes feedback with public ScrollContainer calculations,
+32 -11
View File
@@ -154,22 +154,43 @@ namespace osu.Game.Screens.SelectV2
{
new[]
{
wedgesContainer = new FillFlowContainer
new Container
{
RelativeSizeAxes = Axes.Both,
Margin = new MarginPadding
{
Top = -CORNER_RADIUS_HIDE_OFFSET,
Left = -CORNER_RADIUS_HIDE_OFFSET
},
Spacing = new Vector2(0f, 4f),
Direction = FillDirection.Vertical,
// Ensure the left components are on top of the carousel both visually (although they should never overlay)
// but more importantly, for input purposes to allow the scroll-to-selection logic to override carousel's
// screen-wide scroll handling.
Depth = float.MinValue,
Shear = OsuGame.SHEAR,
Children = new Drawable[]
{
new ShearAligningWrapper(titleWedge = new BeatmapTitleWedge()),
new ShearAligningWrapper(detailsArea = new BeatmapDetailsArea()),
},
new Container
{
// Pad enough to only reset scroll when well into the left wedge areas.
Padding = new MarginPadding { Right = 40 },
RelativeSizeAxes = Axes.Both,
Child = new Select.SongSelect.LeftSideInteractionContainer(() => carousel.ScrollToSelection())
{
RelativeSizeAxes = Axes.Both,
},
},
wedgesContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Margin = new MarginPadding
{
Top = -CORNER_RADIUS_HIDE_OFFSET,
Left = -CORNER_RADIUS_HIDE_OFFSET
},
Spacing = new Vector2(0f, 4f),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new ShearAligningWrapper(titleWedge = new BeatmapTitleWedge()),
new ShearAligningWrapper(detailsArea = new BeatmapDetailsArea()),
},
},
}
},
Empty(),
new Container