From 03adae4417a8b52c5af8bb00672e0a177442f4ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Oct 2025 11:07:06 +0200 Subject: [PATCH 1/3] Scroll song select title wedge text if it overflows Instead of truncating. Addresses https://github.com/ppy/osu/discussions/35404. The one "tiny" problem is that the "click to search" functionality of these texts is maybe a bit worse now, because the clickable target is now the full width of the wedge rather than autosized to the text. Salvaging this is *maybe* possible, but *definitely* annoying, so I'd rather not frontload it. --- osu.Game/Overlays/MarqueeContainer.cs | 14 +++-- osu.Game/Overlays/Music/PlaylistItem.cs | 1 + osu.Game/Overlays/NowPlayingOverlay.cs | 2 + .../Screens/SelectV2/BeatmapTitleWedge.cs | 58 ++++++++++--------- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/MarqueeContainer.cs b/osu.Game/Overlays/MarqueeContainer.cs index 1b0b59abe0..07ef70981f 100644 --- a/osu.Game/Overlays/MarqueeContainer.cs +++ b/osu.Game/Overlays/MarqueeContainer.cs @@ -49,8 +49,15 @@ namespace osu.Game.Overlays private Func? createContent; + public new MarginPadding Padding + { + get => base.Padding; + set => base.Padding = value; + } + + public float OverflowSpacing { get; init; } = 15; + private const float pixels_per_second = 50; - private const float padding = 15; private Drawable mainContent = null!; private Drawable fillerContent = null!; @@ -71,8 +78,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Horizontal, Anchor = NonOverflowingContentAnchor, Origin = NonOverflowingContentAnchor, - Spacing = new Vector2(padding), - Padding = new MarginPadding { Horizontal = padding }, + Spacing = new Vector2(OverflowSpacing), }; } @@ -105,7 +111,7 @@ namespace osu.Game.Overlays flow.Anchor = Anchor.TopLeft; flow.Origin = Anchor.TopLeft; - float targetX = mainContent.DrawWidth + padding; + float targetX = mainContent.DrawWidth + OverflowSpacing; flow.MoveToX(0) .Delay(InitialMoveDelay) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 6217a9bc9e..5cbde6ba57 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -49,6 +49,7 @@ namespace osu.Game.Overlays.Music RelativeSizeAxes = Axes.X, InitialMoveDelay = 0, AllowScrolling = false, + Padding = new MarginPadding { Horizontal = 15 }, }; selectedSet.BindTo(playlistOverlay.SelectedSet); diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 11819cb485..a58aa27e24 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -121,6 +121,7 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, }, NonOverflowingContentAnchor = Anchor.Centre, + Padding = new MarginPadding { Horizontal = 15 }, }, artist = new MarqueeContainer { @@ -136,6 +137,7 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, }, NonOverflowingContentAnchor = Anchor.Centre, + Padding = new MarginPadding { Horizontal = 15 }, }, new Container { diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs index 530b1348dd..69f4aaea4a 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs @@ -20,6 +20,7 @@ using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -49,15 +50,13 @@ namespace osu.Game.Screens.SelectV2 private ModSettingChangeTracker? settingChangeTracker; private BeatmapSetOnlineStatusPill statusPill = null!; - private Container titleContainer = null!; private OsuHoverContainer titleLink = null!; - private OsuSpriteText titleLabel = null!; - private Container artistContainer = null!; + private MarqueeContainer titleLabel = null!; private OsuHoverContainer artistLink = null!; - private OsuSpriteText artistLabel = null!; + private MarqueeContainer artistLabel = null!; - internal string DisplayedTitle => titleLabel.Text.ToString(); - internal string DisplayedArtist => artistLabel.Text.ToString(); + internal string DisplayedTitle { get; private set; } + internal string DisplayedArtist { get; private set; } private StatisticPlayCount playCount = null!; private FavouriteButton favouriteButton = null!; @@ -110,7 +109,7 @@ namespace osu.Game.Screens.SelectV2 TextSize = OsuFont.Style.Caption1.Size, TextPadding = new MarginPadding { Horizontal = 6, Vertical = 1 }, }), - new ShearAligningWrapper(titleContainer = new Container + new ShearAligningWrapper(new Container { Shear = -OsuGame.SHEAR, RelativeSizeAxes = Axes.X, @@ -118,15 +117,15 @@ namespace osu.Game.Screens.SelectV2 Margin = new MarginPadding { Bottom = -4f }, Child = titleLink = new OsuHoverContainer { - AutoSizeAxes = Axes.Both, - Child = titleLabel = new TruncatingSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = titleLabel = new MarqueeContainer { - Shadow = true, - Font = OsuFont.Style.Title, - }, + OverflowSpacing = 50, + } } }), - new ShearAligningWrapper(artistContainer = new Container + new ShearAligningWrapper(new Container { Shear = -OsuGame.SHEAR, RelativeSizeAxes = Axes.X, @@ -134,12 +133,12 @@ namespace osu.Game.Screens.SelectV2 Margin = new MarginPadding { Left = 1f }, Child = artistLink = new OsuHoverContainer { - AutoSizeAxes = Axes.Both, - Child = artistLabel = new TruncatingSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = artistLabel = new MarqueeContainer { - Shadow = true, - Font = OsuFont.Style.Heading2, - }, + OverflowSpacing = 50, + } } }), new ShearAligningWrapper(statisticsFlow = new FillFlowContainer @@ -214,13 +213,6 @@ namespace osu.Game.Screens.SelectV2 .FadeOut(SongSelect.ENTER_DURATION / 3, Easing.In); } - protected override void Update() - { - base.Update(); - titleLabel.MaxWidth = titleContainer.DrawWidth - 20; - artistLabel.MaxWidth = artistContainer.DrawWidth - 20; - } - private void updateDisplay() { var metadata = working.Value.Metadata; @@ -229,12 +221,24 @@ namespace osu.Game.Screens.SelectV2 statusPill.Status = beatmapInfo.Status; var titleText = new RomanisableString(metadata.TitleUnicode, metadata.Title); - titleLabel.Text = titleText; + titleLabel.CreateContent = () => new OsuSpriteText + { + Text = titleText, + Shadow = true, + Font = OsuFont.Style.Title, + }; titleLink.Action = () => songSelect?.Search(titleText.GetPreferred(localisation.CurrentParameters.Value.PreferOriginalScript)); + DisplayedTitle = titleText.ToString(); var artistText = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); - artistLabel.Text = artistText; + artistLabel.CreateContent = () => new OsuSpriteText + { + Text = artistText, + Shadow = true, + Font = OsuFont.Style.Heading2, + }; artistLink.Action = () => songSelect?.Search(artistText.GetPreferred(localisation.CurrentParameters.Value.PreferOriginalScript)); + DisplayedArtist = artistText.ToString(); updateLengthAndBpmStatistics(); updateOnlineDisplay(); From 1a49d030a0b5322b58557f63df8d91218c16c0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Oct 2025 11:08:01 +0200 Subject: [PATCH 2/3] Fix marquee container not updating scrolling state if its content changes size This is actually possible in current usages if you e.g. toggle "use original metadata" on/off which will change the width of the underlying sprite texts. Or by setting window size. Pick your poison. --- osu.Game/Overlays/MarqueeContainer.cs | 39 +++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MarqueeContainer.cs b/osu.Game/Overlays/MarqueeContainer.cs index 07ef70981f..2d651abb00 100644 --- a/osu.Game/Overlays/MarqueeContainer.cs +++ b/osu.Game/Overlays/MarqueeContainer.cs @@ -3,8 +3,10 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; using osuTK; namespace osu.Game.Overlays @@ -21,7 +23,7 @@ namespace osu.Game.Overlays set { allowScrolling = value; - ScheduleAfterChildren(updateScrolling); + scrollCached.Invalidate(); } } @@ -63,8 +65,13 @@ namespace osu.Game.Overlays private Drawable fillerContent = null!; private FillFlowContainer flow = null!; + private readonly Cached scrollCached = new Cached(); + private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); + public MarqueeContainer() { + AddLayout(drawSizeLayout); + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; } @@ -72,13 +79,14 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - InternalChild = flow = new FillFlowContainer + InternalChild = flow = new MarqueeFlow { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Anchor = NonOverflowingContentAnchor, Origin = NonOverflowingContentAnchor, Spacing = new Vector2(OverflowSpacing), + OnRequiredParentSizeInvalidated = () => scrollCached.Invalidate(), }; } @@ -98,12 +106,17 @@ namespace osu.Game.Overlays flow.Add(mainContent = createContent()); flow.Add(fillerContent = createContent().With(d => d.Alpha = 0)); - ScheduleAfterChildren(updateScrolling); + scrollCached.Invalidate(); } - private void updateScrolling() + protected override void UpdateAfterChildren() { - float overflowWidth = mainContent.DrawWidth + padding - DrawWidth; + base.UpdateAfterChildren(); + + if (scrollCached.IsValid && drawSizeLayout.IsValid) + return; + + float overflowWidth = mainContent.DrawWidth - DrawWidth; if (overflowWidth > 0 && AllowScrolling) { @@ -126,6 +139,22 @@ namespace osu.Game.Overlays flow.Anchor = NonOverflowingContentAnchor; flow.Origin = NonOverflowingContentAnchor; } + + scrollCached.Validate(); + drawSizeLayout.Validate(); + } + + private partial class MarqueeFlow : FillFlowContainer + { + public required Action OnRequiredParentSizeInvalidated { get; init; } + + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) + { + if (invalidation.HasFlag(Invalidation.RequiredParentSizeToFit)) + OnRequiredParentSizeInvalidated.Invoke(); + + return base.OnInvalidate(invalidation, source); + } } } } From 33e42d280948f66d9c0cc7797bd474d0cbbb999b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Oct 2025 12:11:38 +0200 Subject: [PATCH 3/3] Fix code quality --- osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs index 69f4aaea4a..a74872eaa7 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge.cs @@ -55,8 +55,8 @@ namespace osu.Game.Screens.SelectV2 private OsuHoverContainer artistLink = null!; private MarqueeContainer artistLabel = null!; - internal string DisplayedTitle { get; private set; } - internal string DisplayedArtist { get; private set; } + internal string DisplayedTitle { get; private set; } = string.Empty; + internal string DisplayedArtist { get; private set; } = string.Empty; private StatisticPlayCount playCount = null!; private FavouriteButton favouriteButton = null!;