1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-18 02:03:01 +08:00

Merge pull request #31535 from peppy/beatmap-carousel-v2-right-click-scroll

Refactor absolute scrolling at song select
This commit is contained in:
Dan Balasescu 2025-01-17 17:02:14 +09:00 committed by GitHub
commit 88829d5321
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 122 additions and 102 deletions

View File

@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private OsuTextFlowContainer stats = null!;
private BeatmapCarousel carousel = null!;
private OsuScrollContainer scroll => carousel.ChildrenOfType<OsuScrollContainer>().Single();
private OsuScrollContainer<Drawable> scroll => carousel.ChildrenOfType<OsuScrollContainer<Drawable>>().Single();
private int beatmapCount;

View File

@ -170,8 +170,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg);
SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false);
SetDefault(OsuSetting.SongSelectRightMouseScroll, false);
SetDefault(OsuSetting.Scaling, ScalingMode.Off);
SetDefault(OsuSetting.SafeAreaConsiderations, true);
SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f, 0.01f);
@ -401,7 +399,6 @@ namespace osu.Game.Configuration
Skin,
ScreenshotFormat,
ScreenshotCaptureMenuCursor,
SongSelectRightMouseScroll,
BeatmapSkins,
BeatmapColours,
BeatmapHitsounds,

View File

@ -26,26 +26,12 @@ namespace osu.Game.Graphics.Containers
}
}
public partial class OsuScrollContainer<T> : ScrollContainer<T> where T : Drawable
public partial class OsuScrollContainer<T> : ScrollContainer<T>
where T : Drawable
{
public const float SCROLL_BAR_WIDTH = 10;
public const float SCROLL_BAR_PADDING = 3;
/// <summary>
/// Allows controlling the scroll bar from any position in the container using the right mouse button.
/// Uses the value of <see cref="DistanceDecayOnRightMouseScrollbar"/> to smoothly scroll to the dragged location.
/// </summary>
public bool RightMouseScrollbar;
/// <summary>
/// Controls the rate with which the target position is approached when performing a relative drag. Default is 0.02.
/// </summary>
public double DistanceDecayOnRightMouseScrollbar = 0.02;
private bool rightMouseDragging;
protected override bool IsDragging => base.IsDragging || rightMouseDragging;
public OsuScrollContainer(Direction scrollDirection = Direction.Vertical)
: base(scrollDirection)
{
@ -71,50 +57,6 @@ namespace osu.Game.Graphics.Containers
ScrollTo(maxPos - DisplayableContent + extraScroll, animated);
}
protected override bool OnMouseDown(MouseDownEvent e)
{
if (shouldPerformRightMouseScroll(e))
{
ScrollFromMouseEvent(e);
return true;
}
return base.OnMouseDown(e);
}
protected override void OnDrag(DragEvent e)
{
if (rightMouseDragging)
{
ScrollFromMouseEvent(e);
return;
}
base.OnDrag(e);
}
protected override bool OnDragStart(DragStartEvent e)
{
if (shouldPerformRightMouseScroll(e))
{
rightMouseDragging = true;
return true;
}
return base.OnDragStart(e);
}
protected override void OnDragEnd(DragEndEvent e)
{
if (rightMouseDragging)
{
rightMouseDragging = false;
return;
}
base.OnDragEnd(e);
}
protected override bool OnScroll(ScrollEvent e)
{
// allow for controlling volume when alt is held.
@ -124,15 +66,22 @@ namespace osu.Game.Graphics.Containers
return base.OnScroll(e);
}
protected virtual void ScrollFromMouseEvent(MouseEvent e)
#region Absolute scrolling
/// <summary>
/// Controls the rate with which the target position is approached when performing a relative drag. Default is 0.02.
/// </summary>
public double DistanceDecayOnAbsoluteScroll = 0.02;
protected virtual void ScrollToAbsolutePosition(Vector2 screenSpacePosition)
{
float fromScrollbarPosition = FromScrollbarPosition(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim]);
float fromScrollbarPosition = FromScrollbarPosition(ToLocalSpace(screenSpacePosition)[ScrollDim]);
float scrollbarCentreOffset = FromScrollbarPosition(Scrollbar.DrawHeight) * 0.5f;
ScrollTo(Clamp(fromScrollbarPosition - scrollbarCentreOffset), true, DistanceDecayOnRightMouseScrollbar);
ScrollTo(Clamp(fromScrollbarPosition - scrollbarCentreOffset), true, DistanceDecayOnAbsoluteScroll);
}
private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right;
#endregion
protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction);

View File

@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osuTK;
namespace osu.Game.Graphics.Containers
{
@ -47,10 +47,10 @@ namespace osu.Game.Graphics.Containers
base.ScrollIntoView(target, animated);
}
protected override void ScrollFromMouseEvent(MouseEvent e)
protected override void ScrollToAbsolutePosition(Vector2 screenSpacePosition)
{
UserScrolling = true;
base.ScrollFromMouseEvent(e);
base.ScrollToAbsolutePosition(screenSpacePosition);
}
public new void ScrollTo(double value, bool animated = true, double? distanceDecay = null)

View File

@ -204,6 +204,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods),
new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseModSpeed),
new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseModSpeed),
new KeyBinding(new[] { InputKey.MouseRight }, GlobalAction.AbsoluteScrollSongList),
};
private static IEnumerable<KeyBinding> audioControlKeyBindings => new[]
@ -490,6 +491,9 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextBookmark))]
EditorSeekToNextBookmark,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.AbsoluteScrollSongList))]
AbsoluteScrollSongList
}
public enum GlobalActionCategory

View File

@ -449,6 +449,11 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString EditorSeekToNextBookmark => new TranslatableString(getKey(@"editor_seek_to_next_bookmark"), @"Seek to next bookmark");
/// <summary>
/// "Absolute scroll song list"
/// </summary>
public static LocalisableString AbsoluteScrollSongList => new TranslatableString(getKey(@"absolute_scroll_song_list"), @"Absolute scroll song list");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -19,12 +19,6 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
Children = new Drawable[]
{
new SettingsCheckbox
{
ClassicDefault = true,
LabelText = UserInterfaceStrings.RightMouseScroll,
Current = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll),
},
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,

View File

@ -14,6 +14,7 @@ using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@ -184,8 +185,6 @@ namespace osu.Game.Screens.Select
private readonly Cached itemsCache = new Cached();
private PendingScrollOperation pendingScrollOperation = PendingScrollOperation.None;
public Bindable<bool> RightClickScrollingEnabled = new Bindable<bool>();
public Bindable<RandomSelectAlgorithm> RandomAlgorithm = new Bindable<RandomSelectAlgorithm>();
private readonly List<CarouselBeatmapSet> previouslyVisitedRandomSets = new List<CarouselBeatmapSet>();
private readonly List<CarouselBeatmap> randomSelectedBeatmaps = new List<CarouselBeatmap>();
@ -226,9 +225,6 @@ namespace osu.Game.Screens.Select
randomSelectSample = audio.Samples.Get(@"SongSelect/select-random");
config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm);
config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled);
RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true);
detachedBeatmapSets = beatmaps.GetBeatmapSets(cancellationToken);
detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged);
@ -1161,10 +1157,8 @@ namespace osu.Game.Screens.Select
}
}
public partial class CarouselScrollContainer : UserTrackingScrollContainer<DrawableCarouselItem>
public partial class CarouselScrollContainer : UserTrackingScrollContainer<DrawableCarouselItem>, IKeyBindingHandler<GlobalAction>
{
private bool rightMouseScrollBlocked;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public CarouselScrollContainer()
@ -1176,31 +1170,54 @@ namespace osu.Game.Screens.Select
Masking = false;
}
protected override bool OnMouseDown(MouseDownEvent e)
#region Absolute scrolling
private bool absoluteScrolling;
protected override bool IsDragging => base.IsDragging || absoluteScrolling;
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (e.Button == MouseButton.Right)
switch (e.Action)
{
// we need to block right click absolute scrolling when hovering a carousel item so context menus can display.
// this can be reconsidered when we have an alternative to right click scrolling.
if (GetContainingInputManager()!.HoveredDrawables.OfType<DrawableCarouselItem>().Any())
{
rightMouseScrollBlocked = true;
return false;
}
case GlobalAction.AbsoluteScrollSongList:
// The default binding for absolute scroll is right mouse button.
// To avoid conflicts with context menus, disallow absolute scroll completely if it looks like things will fall over.
if (e.CurrentState.Mouse.Buttons.Contains(MouseButton.Right)
&& GetContainingInputManager()!.HoveredDrawables.OfType<IHasContextMenu>().Any())
return false;
ScrollToAbsolutePosition(e.CurrentState.Mouse.Position);
absoluteScrolling = true;
return true;
}
rightMouseScrollBlocked = false;
return base.OnMouseDown(e);
return false;
}
protected override bool OnDragStart(DragStartEvent e)
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
if (rightMouseScrollBlocked)
return false;
return base.OnDragStart(e);
switch (e.Action)
{
case GlobalAction.AbsoluteScrollSongList:
absoluteScrolling = false;
break;
}
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (absoluteScrolling)
{
ScrollToAbsolutePosition(e.CurrentState.Mouse.Position);
return true;
}
return base.OnMouseMove(e);
}
#endregion
protected override ScrollbarContainer CreateScrollbar(Direction direction)
{
return new PaddedScrollbar();

View File

@ -11,13 +11,18 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Logging;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Screens.SelectV2
{
@ -390,7 +395,7 @@ namespace osu.Game.Screens.SelectV2
/// Implementation of scroll container which handles very large vertical lists by internally using <c>double</c> precision
/// for pre-display Y values.
/// </summary>
private partial class CarouselScrollContainer : OsuScrollContainer
private partial class CarouselScrollContainer : UserTrackingScrollContainer, IKeyBindingHandler<GlobalAction>
{
public readonly Container Panels;
@ -465,6 +470,55 @@ namespace osu.Game.Screens.SelectV2
foreach (var d in Panels)
d.Y = (float)(((ICarouselPanel)d).DrawYPosition + scrollableExtent);
}
#region Absolute scrolling
private bool absoluteScrolling;
protected override bool IsDragging => base.IsDragging || absoluteScrolling;
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
switch (e.Action)
{
case GlobalAction.AbsoluteScrollSongList:
// The default binding for absolute scroll is right mouse button.
// To avoid conflicts with context menus, disallow absolute scroll completely if it looks like things will fall over.
if (e.CurrentState.Mouse.Buttons.Contains(MouseButton.Right)
&& GetContainingInputManager()!.HoveredDrawables.OfType<IHasContextMenu>().Any())
return false;
ScrollToAbsolutePosition(e.CurrentState.Mouse.Position);
absoluteScrolling = true;
return true;
}
return false;
}
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
switch (e.Action)
{
case GlobalAction.AbsoluteScrollSongList:
absoluteScrolling = false;
break;
}
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (absoluteScrolling)
{
ScrollToAbsolutePosition(e.CurrentState.Mouse.Position);
return true;
}
return base.OnMouseMove(e);
}
#endregion
}
private class BoundsCarouselItem : CarouselItem