1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-30 20:32:55 +08:00

Merge branch 'master' into user-panel-status

This commit is contained in:
Dean Herbert 2025-01-17 17:37:09 +09:00 committed by GitHub
commit 6bbfd362b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 242 additions and 116 deletions

View File

@ -41,6 +41,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Carousel;
@ -317,6 +318,92 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for song select", () => songSelect.IsCurrentScreen()); AddUntilStep("wait for song select", () => songSelect.IsCurrentScreen());
} }
[Test]
public void TestOffsetAdjustDuringPause()
{
Player player = null;
Screens.Select.SongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail() });
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () =>
{
DismissAnyNotifications();
player = Game.ScreenStack.CurrentScreen as Player;
return player?.IsLoaded == true;
});
AddUntilStep("wait for track playing", () => Game.Beatmap.Value.Track.IsRunning);
checkOffset(0);
AddStep("adjust offset via keyboard", () => InputManager.Key(Key.Minus));
checkOffset(-1);
AddStep("pause", () => player.ChildrenOfType<GameplayClockContainer>().First().Stop());
AddUntilStep("wait for pause", () => player.ChildrenOfType<GameplayClockContainer>().First().IsPaused.Value, () => Is.True);
AddStep("attempt adjust offset via keyboard", () => InputManager.Key(Key.Minus));
checkOffset(-1);
void checkOffset(double offset)
{
AddUntilStep($"control offset is {offset}", () => this.ChildrenOfType<GameplayOffsetControl>().Single().ChildrenOfType<BeatmapOffsetControl>().Single().Current.Value,
() => Is.EqualTo(offset));
AddUntilStep($"database offset is {offset}", () => Game.BeatmapManager.QueryBeatmap(b => b.ID == Game.Beatmap.Value.BeatmapInfo.ID)!.UserSettings.Offset,
() => Is.EqualTo(offset));
}
}
[Test]
public void TestOffsetAdjustDuringGameplay()
{
Player player = null;
Screens.Select.SongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game).WaitSafely());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail() });
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () =>
{
DismissAnyNotifications();
player = Game.ScreenStack.CurrentScreen as Player;
return player?.IsLoaded == true;
});
AddUntilStep("wait for track playing", () => Game.Beatmap.Value.Track.IsRunning);
checkOffset(0);
AddStep("adjust offset via keyboard", () => InputManager.Key(Key.Minus));
checkOffset(-1);
AddStep("seek beyond 10 seconds", () => player.ChildrenOfType<GameplayClockContainer>().First().Seek(10500));
AddUntilStep("wait for seek", () => player.ChildrenOfType<GameplayClockContainer>().First().CurrentTime, () => Is.GreaterThan(10600));
AddStep("attempt adjust offset via keyboard", () => InputManager.Key(Key.Minus));
checkOffset(-1);
void checkOffset(double offset)
{
AddUntilStep($"control offset is {offset}", () => this.ChildrenOfType<GameplayOffsetControl>().Single().ChildrenOfType<BeatmapOffsetControl>().Single().Current.Value,
() => Is.EqualTo(offset));
AddUntilStep($"database offset is {offset}", () => Game.BeatmapManager.QueryBeatmap(b => b.ID == Game.Beatmap.Value.BeatmapInfo.ID)!.UserSettings.Offset,
() => Is.EqualTo(offset));
}
}
[Test] [Test]
public void TestRetryCountIncrements() public void TestRetryCountIncrements()
{ {

View File

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

View File

@ -170,8 +170,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg);
SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false); SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false);
SetDefault(OsuSetting.SongSelectRightMouseScroll, false);
SetDefault(OsuSetting.Scaling, ScalingMode.Off); SetDefault(OsuSetting.Scaling, ScalingMode.Off);
SetDefault(OsuSetting.SafeAreaConsiderations, true); SetDefault(OsuSetting.SafeAreaConsiderations, true);
SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f, 0.01f); SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f, 0.01f);
@ -401,7 +399,6 @@ namespace osu.Game.Configuration
Skin, Skin,
ScreenshotFormat, ScreenshotFormat,
ScreenshotCaptureMenuCursor, ScreenshotCaptureMenuCursor,
SongSelectRightMouseScroll,
BeatmapSkins, BeatmapSkins,
BeatmapColours, BeatmapColours,
BeatmapHitsounds, 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_WIDTH = 10;
public const float SCROLL_BAR_PADDING = 3; 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) public OsuScrollContainer(Direction scrollDirection = Direction.Vertical)
: base(scrollDirection) : base(scrollDirection)
{ {
@ -71,50 +57,6 @@ namespace osu.Game.Graphics.Containers
ScrollTo(maxPos - DisplayableContent + extraScroll, animated); 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) protected override bool OnScroll(ScrollEvent e)
{ {
// allow for controlling volume when alt is held. // allow for controlling volume when alt is held.
@ -124,15 +66,22 @@ namespace osu.Game.Graphics.Containers
return base.OnScroll(e); 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; 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); 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events; using osuTK;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
@ -47,10 +47,10 @@ namespace osu.Game.Graphics.Containers
base.ScrollIntoView(target, animated); base.ScrollIntoView(target, animated);
} }
protected override void ScrollFromMouseEvent(MouseEvent e) protected override void ScrollToAbsolutePosition(Vector2 screenSpacePosition)
{ {
UserScrolling = true; UserScrolling = true;
base.ScrollFromMouseEvent(e); base.ScrollToAbsolutePosition(screenSpacePosition);
} }
public new void ScrollTo(double value, bool animated = true, double? distanceDecay = null) 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(InputKey.BackSpace, GlobalAction.DeselectAllMods),
new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseModSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseModSpeed),
new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseModSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseModSpeed),
new KeyBinding(new[] { InputKey.MouseRight }, GlobalAction.AbsoluteScrollSongList),
}; };
private static IEnumerable<KeyBinding> audioControlKeyBindings => new[] private static IEnumerable<KeyBinding> audioControlKeyBindings => new[]
@ -490,6 +491,9 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextBookmark))] [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextBookmark))]
EditorSeekToNextBookmark, EditorSeekToNextBookmark,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.AbsoluteScrollSongList))]
AbsoluteScrollSongList
} }
public enum GlobalActionCategory public enum GlobalActionCategory

View File

@ -449,6 +449,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString EditorSeekToNextBookmark => new TranslatableString(getKey(@"editor_seek_to_next_bookmark"), @"Seek to next bookmark"); 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}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

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

View File

@ -322,6 +322,7 @@ namespace osu.Game.Screens.Play
} }
dependencies.CacheAs(DrawableRuleset.FrameStableClock); dependencies.CacheAs(DrawableRuleset.FrameStableClock);
dependencies.CacheAs<IGameplayClock>(DrawableRuleset.FrameStableClock);
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
// also give the overlays the ruleset skin provider to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) // also give the overlays the ruleset skin provider to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)

View File

@ -274,20 +274,36 @@ namespace osu.Game.Screens.Play.PlayerSettings
beatmapOffsetSubscription?.Dispose(); beatmapOffsetSubscription?.Dispose();
} }
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) protected override void Update()
{
base.Update();
Current.Disabled = !allowOffsetAdjust;
}
private bool allowOffsetAdjust
{
get
{ {
// General limitations to ensure players don't do anything too weird. // General limitations to ensure players don't do anything too weird.
// These match stable for now. // These match stable for now.
if (player is SubmittingPlayer) if (player is SubmittingPlayer)
{ {
Debug.Assert(gameplayClock != null);
// TODO: the blocking conditions should probably display a message. // TODO: the blocking conditions should probably display a message.
if (player?.IsBreakTime.Value == false && gameplayClock?.CurrentTime - gameplayClock?.StartTime > 10000) if (!player.IsBreakTime.Value && gameplayClock.CurrentTime - gameplayClock.StartTime > 10000)
return false; return false;
if (gameplayClock?.IsPaused.Value == true) if (gameplayClock.IsPaused.Value)
return false; return false;
} }
return true;
}
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
// To match stable, this should adjust by 5 ms, or 1 ms when holding alt. // To match stable, this should adjust by 5 ms, or 1 ms when holding alt.
// But that is hard to make work with global actions due to the operating mode. // But that is hard to make work with global actions due to the operating mode.
// Let's use the more precise as a default for now. // Let's use the more precise as a default for now.
@ -296,10 +312,12 @@ namespace osu.Game.Screens.Play.PlayerSettings
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.IncreaseOffset: case GlobalAction.IncreaseOffset:
if (!Current.Disabled)
Current.Value += amount; Current.Value += amount;
return true; return true;
case GlobalAction.DecreaseOffset: case GlobalAction.DecreaseOffset:
if (!Current.Disabled)
Current.Value -= amount; Current.Value -= amount;
return true; return true;
} }

View File

@ -14,6 +14,7 @@ using osu.Framework.Bindables;
using osu.Framework.Caching; using osu.Framework.Caching;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
@ -184,8 +185,6 @@ namespace osu.Game.Screens.Select
private readonly Cached itemsCache = new Cached(); private readonly Cached itemsCache = new Cached();
private PendingScrollOperation pendingScrollOperation = PendingScrollOperation.None; private PendingScrollOperation pendingScrollOperation = PendingScrollOperation.None;
public Bindable<bool> RightClickScrollingEnabled = new Bindable<bool>();
public Bindable<RandomSelectAlgorithm> RandomAlgorithm = new Bindable<RandomSelectAlgorithm>(); public Bindable<RandomSelectAlgorithm> RandomAlgorithm = new Bindable<RandomSelectAlgorithm>();
private readonly List<CarouselBeatmapSet> previouslyVisitedRandomSets = new List<CarouselBeatmapSet>(); private readonly List<CarouselBeatmapSet> previouslyVisitedRandomSets = new List<CarouselBeatmapSet>();
private readonly List<CarouselBeatmap> randomSelectedBeatmaps = new List<CarouselBeatmap>(); private readonly List<CarouselBeatmap> randomSelectedBeatmaps = new List<CarouselBeatmap>();
@ -226,9 +225,6 @@ namespace osu.Game.Screens.Select
randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); randomSelectSample = audio.Samples.Get(@"SongSelect/select-random");
config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm);
config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled);
RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true);
detachedBeatmapSets = beatmaps.GetBeatmapSets(cancellationToken); detachedBeatmapSets = beatmaps.GetBeatmapSets(cancellationToken);
detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); 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 override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public CarouselScrollContainer() public CarouselScrollContainer()
@ -1176,31 +1170,54 @@ namespace osu.Game.Screens.Select
Masking = false; Masking = false;
} }
protected override bool OnMouseDown(MouseDownEvent e) #region Absolute scrolling
{
if (e.Button == MouseButton.Right)
{
// 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;
}
}
rightMouseScrollBlocked = false; private bool absoluteScrolling;
return base.OnMouseDown(e);
}
protected override bool OnDragStart(DragStartEvent e) protected override bool IsDragging => base.IsDragging || absoluteScrolling;
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (rightMouseScrollBlocked) 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; return false;
return base.OnDragStart(e); 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
protected override ScrollbarContainer CreateScrollbar(Direction direction) protected override ScrollbarContainer CreateScrollbar(Direction direction)
{ {
return new PaddedScrollbar(); return new PaddedScrollbar();

View File

@ -11,13 +11,18 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Screens.SelectV2 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 /// Implementation of scroll container which handles very large vertical lists by internally using <c>double</c> precision
/// for pre-display Y values. /// for pre-display Y values.
/// </summary> /// </summary>
private partial class CarouselScrollContainer : OsuScrollContainer private partial class CarouselScrollContainer : UserTrackingScrollContainer, IKeyBindingHandler<GlobalAction>
{ {
public readonly Container Panels; public readonly Container Panels;
@ -465,6 +470,55 @@ namespace osu.Game.Screens.SelectV2
foreach (var d in Panels) foreach (var d in Panels)
d.Y = (float)(((ICarouselPanel)d).DrawYPosition + scrollableExtent); 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 private class BoundsCarouselItem : CarouselItem