From 6dc343273554b59fce91a3f188ce3b3ce47731e7 Mon Sep 17 00:00:00 2001 From: AeroKoder Date: Wed, 17 Sep 2025 12:44:43 -0700 Subject: [PATCH 01/22] Fix certain slider shapes incorrectly registering as a horizontal/vertical only slider. --- .../Edit/OsuSelectionHandler.cs | 2 +- .../Edit/OsuSelectionScaleHandler.cs | 8 ++------ osu.Game/Utils/GeometryUtils.cs | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 3a1ff34fb9..c591b79b29 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; - Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects); + Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects, true); Vector2 delta = Vector2.Zero; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 9a5d3c3bc1..3072e5d11b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -81,12 +81,8 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); - OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider - ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) - : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); - originalConvexHull = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider2 - ? GeometryUtils.GetConvexHull(slider2.Path.ControlPoints.Select(p => slider2.Position + p.Position)) - : GeometryUtils.GetConvexHull(objectsInScale.Keys); + OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); + originalConvexHull = GeometryUtils.GetConvexHull(objectsInScale.Keys); defaultOrigin = GeometryUtils.MinimumEnclosingCircle(originalConvexHull).Item1; } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index eac86a9c02..185b1cc4f1 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -144,8 +144,9 @@ namespace osu.Game.Utils /// Returns a gamefield-space quad surrounding the provided hit objects. /// /// The hit objects to calculate a quad for. - public static Quad GetSurroundingQuad(IEnumerable hitObjects) => - GetSurroundingQuad(enumerateStartAndEndPositions(hitObjects)); + /// Whether to only include the start and end positions of the slider, or include every control point in the slider. + public static Quad GetSurroundingQuad(IEnumerable hitObjects, bool startAndEndOnly = false) => + GetSurroundingQuad(startAndEndOnly ? enumerateStartAndEndPositions(hitObjects) : enumeratePositions(hitObjects)); /// /// Returns the points that make up the convex hull of the provided points. @@ -202,7 +203,7 @@ namespace osu.Game.Utils } public static List GetConvexHull(IEnumerable hitObjects) => - GetConvexHull(enumerateStartAndEndPositions(hitObjects)); + GetConvexHull(enumeratePositions(hitObjects)); private static IEnumerable enumerateStartAndEndPositions(IEnumerable hitObjects) => hitObjects.SelectMany(h => @@ -220,6 +221,17 @@ namespace osu.Game.Utils return new[] { h.Position }; }); + private static IEnumerable enumeratePositions(IEnumerable hitObjects) => + hitObjects.SelectMany(h => + { + if (h is IHasPath path) + { + return path.Path.ControlPoints.Select(p => h.Position + p.Position); + } + + return new[] { h.Position }; + }); + #region Welzl helpers // Function to check whether a point lies inside or on the boundaries of the circle From 702511c918cf3eac88b5577d17244f8569ca6e1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 14:22:55 +0900 Subject: [PATCH 02/22] More matchmaking renames and spacing adjusts --- ...nGrid.cs => TestSceneBeatmapSelectGrid.cs} | 27 +++++++++++++++---- ...anel.cs => TestSceneBeatmapSelectPanel.cs} | 6 ++--- .../Matchmaking/TestScenePlayerPanel.cs | 4 +-- .../Matchmaking/TestSceneUserPanelOverlay.cs | 4 +-- ...{SelectionGrid.cs => BeatmapSelectGrid.cs} | 22 +++++++-------- ...electionPanel.cs => BeatmapSelectPanel.cs} | 4 +-- .../BeatmapSelect/SubScreenBeatmapSelect.cs | 17 ++++++------ .../Matchmaking/Match/MatchmakingAvatar.cs | 2 +- ...MatchmakingUserPanel.cs => PlayerPanel.cs} | 5 ++-- ...rPanelOverlay.cs => PlayerPanelOverlay.cs} | 22 +++++++-------- .../Match/ScreenMatchmaking.ScreenStack.cs | 4 +-- 11 files changed, 67 insertions(+), 50 deletions(-) rename osu.Game.Tests/Visual/Matchmaking/{TestSceneSelectionGrid.cs => TestSceneBeatmapSelectGrid.cs} (84%) rename osu.Game.Tests/Visual/Matchmaking/{TestSceneSelectionPanel.cs => TestSceneBeatmapSelectPanel.cs} (88%) rename osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/{SelectionGrid.cs => BeatmapSelectGrid.cs} (94%) rename osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/{SelectionPanel.cs => BeatmapSelectPanel.cs} (99%) rename osu.Game/Screens/OnlinePlay/Matchmaking/Match/{MatchmakingUserPanel.cs => PlayerPanel.cs} (98%) rename osu.Game/Screens/OnlinePlay/Matchmaking/Match/{UserPanelOverlay.cs => PlayerPanelOverlay.cs} (93%) diff --git a/osu.Game.Tests/Visual/Matchmaking/TestSceneSelectionGrid.cs b/osu.Game.Tests/Visual/Matchmaking/TestSceneBeatmapSelectGrid.cs similarity index 84% rename from osu.Game.Tests/Visual/Matchmaking/TestSceneSelectionGrid.cs rename to osu.Game.Tests/Visual/Matchmaking/TestSceneBeatmapSelectGrid.cs index 6fba5af070..93a33bdd95 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestSceneSelectionGrid.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestSceneBeatmapSelectGrid.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect; using osu.Game.Tests.Visual.OnlinePlay; @@ -17,11 +18,11 @@ using osuTK; namespace osu.Game.Tests.Visual.Matchmaking { - public partial class TestSceneSelectionGrid : OnlinePlayTestScene + public partial class TestSceneBeatmapSelectGrid : OnlinePlayTestScene { private MultiplayerPlaylistItem[] items = null!; - private SelectionGrid grid = null!; + private BeatmapSelectGrid grid = null!; [Resolved] private BeatmapManager beatmapManager { get; set; } = null!; @@ -58,7 +59,7 @@ namespace osu.Game.Tests.Visual.Matchmaking { base.SetUpSteps(); - AddStep("add grid", () => Child = grid = new SelectionGrid + AddStep("add grid", () => Child = grid = new BeatmapSelectGrid { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -82,6 +83,22 @@ namespace osu.Game.Tests.Visual.Matchmaking { // test scene is weird. }); + + AddStep("add selection 1", () => grid.ChildrenOfType().First().AddUser(new APIUser + { + Id = 6411631, + Username = "Maarvin", + }, isOwnUser: true)); + AddStep("add selection 2", () => grid.ChildrenOfType().Skip(5).First().AddUser(new APIUser + { + Id = 2, + Username = "peppy", + })); + AddStep("add selection 3", () => grid.ChildrenOfType().Skip(10).First().AddUser(new APIUser + { + Id = 1040328, + Username = "smoogipoo", + })); } [Test] @@ -154,7 +171,7 @@ namespace osu.Game.Tests.Visual.Matchmaking var (candidateItems, _) = pickRandomItems(count); grid.TransferCandidatePanelsToRollContainer(candidateItems); - grid.Delay(SelectionGrid.ARRANGE_DELAY) + grid.Delay(BeatmapSelectGrid.ARRANGE_DELAY) .Schedule(() => grid.ArrangeItemsForRollAnimation()); }); @@ -162,7 +179,7 @@ namespace osu.Game.Tests.Visual.Matchmaking AddStep("display roll order", () => { - var panels = grid.ChildrenOfType().ToArray(); + var panels = grid.ChildrenOfType().ToArray(); for (int i = 0; i < panels.Length; i++) { diff --git a/osu.Game.Tests/Visual/Matchmaking/TestSceneSelectionPanel.cs b/osu.Game.Tests/Visual/Matchmaking/TestSceneBeatmapSelectPanel.cs similarity index 88% rename from osu.Game.Tests/Visual/Matchmaking/TestSceneSelectionPanel.cs rename to osu.Game.Tests/Visual/Matchmaking/TestSceneBeatmapSelectPanel.cs index 6745802b30..2de4d6d7ea 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestSceneSelectionPanel.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestSceneBeatmapSelectPanel.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual.Multiplayer; namespace osu.Game.Tests.Visual.Matchmaking { - public partial class TestSceneSelectionPanel : MultiplayerTestScene + public partial class TestSceneBeatmapSelectPanel : MultiplayerTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); @@ -20,9 +20,9 @@ namespace osu.Game.Tests.Visual.Matchmaking [Test] public void TestBeatmapPanel() { - SelectionPanel? panel = null; + BeatmapSelectPanel? panel = null; - AddStep("add panel", () => Child = panel = new SelectionPanel(new MultiplayerPlaylistItem()) + AddStep("add panel", () => Child = panel = new BeatmapSelectPanel(new MultiplayerPlaylistItem()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs b/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs index a7c14cfd94..1ef5e2edc1 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Matchmaking { public partial class TestScenePlayerPanel : MultiplayerTestScene { - private MatchmakingUserPanel panel = null!; + private PlayerPanel panel = null!; public override void SetUpSteps() { @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Matchmaking AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking))); WaitForJoined(); - AddStep("add panel", () => Child = panel = new MatchmakingUserPanel(new MultiplayerRoomUser(1) + AddStep("add panel", () => Child = panel = new PlayerPanel(new MultiplayerRoomUser(1) { User = new APIUser { diff --git a/osu.Game.Tests/Visual/Matchmaking/TestSceneUserPanelOverlay.cs b/osu.Game.Tests/Visual/Matchmaking/TestSceneUserPanelOverlay.cs index 9ed233a507..f48e489370 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestSceneUserPanelOverlay.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestSceneUserPanelOverlay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Matchmaking { public partial class TestSceneUserPanelOverlay : MultiplayerTestScene { - private UserPanelOverlay list = null!; + private PlayerPanelOverlay list = null!; public override void SetUpSteps() { @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Matchmaking Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Size = new Vector2(0.8f), - Child = list = new UserPanelOverlay() + Child = list = new PlayerPanelOverlay() }); } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SelectionGrid.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs similarity index 94% rename from osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SelectionGrid.cs rename to osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs index bd75514b30..209c6f553a 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SelectionGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs @@ -22,7 +22,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { - public partial class SelectionGrid : CompositeDrawable + public partial class BeatmapSelectGrid : CompositeDrawable { public const double ARRANGE_DELAY = 200; @@ -30,17 +30,17 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect private const double arrange_duration = 1000; private const double roll_duration = 4000; private const double present_beatmap_delay = 1200; - private const float panel_spacing = 20; + private const float panel_spacing = 4; public event Action? ItemSelected; [Resolved] private IAPIProvider api { get; set; } = null!; - private readonly Dictionary panelLookup = new Dictionary(); + private readonly Dictionary panelLookup = new Dictionary(); private readonly PanelGridContainer panelGridContainer; - private readonly Container rollContainer; + private readonly Container rollContainer; private readonly OsuScrollContainer scroll; private bool allowSelection = true; @@ -51,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect private Sample? swooshSample; private double? lastSamplePlayback; - public SelectionGrid() + public BeatmapSelectGrid() { InternalChildren = new Drawable[] { @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect Spacing = new Vector2(panel_spacing) }, }, - rollContainer = new Container + rollContainer = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public void AddItem(MultiplayerPlaylistItem item) { - var panel = panelLookup[item.ID] = new SelectionPanel(item) + var panel = panelLookup[item.ID] = new BeatmapSelectPanel(item) { Size = new Vector2(300, 70), AllowSelection = allowSelection, @@ -176,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect var rng = new Random(); - var remainingPanels = new List(); + var remainingPanels = new List(); foreach (var panel in panelGridContainer.Children.ToArray()) { @@ -216,7 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { var panel = rollContainer.Children[i]; - var position = positions[i] * (SelectionPanel.SIZE + new Vector2(panel_spacing)); + var position = positions[i] * (BeatmapSelectPanel.SIZE + new Vector2(panel_spacing)); panel.MoveTo(position, duration + stagger * i, new SplitEasingFunction(Easing.InCubic, Easing.OutExpo, 0.3f)); @@ -285,7 +285,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect while ((numSteps - 1) % rollContainer.Children.Count != finalItemIndex) numSteps++; - SelectionPanel? lastPanel = null; + BeatmapSelectPanel? lastPanel = null; for (int i = 0; i < numSteps; i++) { @@ -346,7 +346,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect PresentRolledBeatmap(finalItem); } - private partial class PanelGridContainer : FillFlowContainer + private partial class PanelGridContainer : FillFlowContainer { public bool LayoutDisabled; diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SelectionPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs similarity index 99% rename from osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SelectionPanel.cs rename to osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs index 1a51ddac64..ab89dcd65f 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SelectionPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs @@ -28,7 +28,7 @@ using osuTK.Input; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { - public partial class SelectionPanel : Container + public partial class BeatmapSelectPanel : Container { public static readonly Vector2 SIZE = new Vector2(300, 70); @@ -52,7 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public override bool PropagatePositionalInputSubTree => AllowSelection; - public SelectionPanel(MultiplayerPlaylistItem item) + public BeatmapSelectPanel(MultiplayerPlaylistItem item) { Item = item; Size = SIZE; diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SubScreenBeatmapSelect.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SubScreenBeatmapSelect.cs index cf86deeb3e..4b34125517 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SubScreenBeatmapSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/SubScreenBeatmapSelect.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public override PanelDisplayStyle PlayersDisplayStyle => PanelDisplayStyle.Split; public override Drawable PlayersDisplayArea { get; } - private readonly SelectionGrid selectionGrid; + private readonly BeatmapSelectGrid beatmapSelectGrid; [Resolved] private MultiplayerClient client { get; set; } = null!; @@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 200 }, - Child = selectionGrid = new SelectionGrid + Child = beatmapSelectGrid = new BeatmapSelectGrid { RelativeSizeAxes = Axes.Both, }, @@ -37,8 +37,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5 }, - Child = PlayersDisplayArea = Empty().With(d => + Child = PlayersDisplayArea = new Container().With(d => { d.RelativeSizeAxes = Axes.Both; }) @@ -55,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect foreach (var item in client.Room!.Playlist) onItemAdded(item); - selectionGrid.ItemSelected += item => client.MatchmakingToggleSelection(item.ID); + beatmapSelectGrid.ItemSelected += item => client.MatchmakingToggleSelection(item.ID); client.MatchmakingItemSelected += onItemSelected; client.MatchmakingItemDeselected += onItemDeselected; @@ -66,22 +65,22 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect if (item.Expired) return; - selectionGrid.AddItem(item); + beatmapSelectGrid.AddItem(item); }); private void onItemSelected(int userId, long itemId) { var user = client.Room!.Users.First(it => it.UserID == userId).User!; - selectionGrid.SetUserSelection(user, itemId, true); + beatmapSelectGrid.SetUserSelection(user, itemId, true); } private void onItemDeselected(int userId, long itemId) { var user = client.Room!.Users.First(it => it.UserID == userId).User!; - selectionGrid.SetUserSelection(user, itemId, false); + beatmapSelectGrid.SetUserSelection(user, itemId, false); } - public void RollFinalBeatmap(long[] candidateItems, long finalItem) => selectionGrid.RollAndDisplayFinalBeatmap(candidateItems, finalItem); + public void RollFinalBeatmap(long[] candidateItems, long finalItem) => beatmapSelectGrid.RollAndDisplayFinalBeatmap(candidateItems, finalItem); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/MatchmakingAvatar.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/MatchmakingAvatar.cs index faf32c6604..53db2114c7 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/MatchmakingAvatar.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/MatchmakingAvatar.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match { /// /// A circular player avatar used in matchmaking displays. - /// Is part of a but can also be used in isolation for a more ambient/decorative user display. + /// Is part of a but can also be used in isolation for a more ambient/decorative user display. /// public partial class MatchmakingAvatar : CompositeDrawable { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/MatchmakingUserPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanel.cs similarity index 98% rename from osu.Game/Screens/OnlinePlay/Matchmaking/Match/MatchmakingUserPanel.cs rename to osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanel.cs index ce4b471df4..f18a33c830 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/MatchmakingUserPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanel.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match /// A panel used throughout matchmaking to represent a user, including local information like their /// rank and high level statistics in the matchmaking system. /// - public partial class MatchmakingUserPanel : UserPanel + public partial class PlayerPanel : UserPanel { public static readonly Vector2 SIZE_HORIZONTAL = new Vector2(250, 100); public static readonly Vector2 SIZE_VERTICAL = new Vector2(150, 200); @@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match private bool horizontal; - public MatchmakingUserPanel(MultiplayerRoomUser user) + public PlayerPanel(MultiplayerRoomUser user) : base(user.User!) { RoomUser = user; @@ -66,6 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match { Masking = true; CornerRadius = 10; + CornerExponent = 10; Add(scaleContainer = new Container { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/UserPanelOverlay.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanelOverlay.cs similarity index 93% rename from osu.Game/Screens/OnlinePlay/Matchmaking/Match/UserPanelOverlay.cs rename to osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanelOverlay.cs index 9ddddda710..8ce080f633 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/UserPanelOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanelOverlay.cs @@ -18,12 +18,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match /// A component which maintains the layout of the players in a matchmaking room. /// Can be controlled to display the panels in a certain location and in multiple styles. /// - public partial class UserPanelOverlay : CompositeDrawable + public partial class PlayerPanelOverlay : CompositeDrawable { [Resolved] private MultiplayerClient client { get; set; } = null!; - private Container panels = null!; + private Container panels = null!; private PlayerPanelCellContainer gridLayout = null!; private PlayerPanelCellContainer splitLayoutLeft = null!; private PlayerPanelCellContainer splitLayoutRight = null!; @@ -40,7 +40,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match gridLayout = new PlayerPanelCellContainer { RelativeSizeAxes = Axes.Both, - Spacing = new Vector2(20, 5), + Spacing = new Vector2(20), }, splitLayoutLeft = new PlayerPanelCellContainer { @@ -49,7 +49,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Vertical, - Spacing = new Vector2(20, 5), + Spacing = new Vector2(5), }, splitLayoutRight = new PlayerPanelCellContainer { @@ -58,9 +58,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Vertical, - Spacing = new Vector2(20, 5), + Spacing = new Vector2(5), }, - panels = new Container + panels = new Container { RelativeSizeAxes = Axes.Both } @@ -110,7 +110,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match private void onUserJoined(MultiplayerRoomUser user) => Scheduler.Add(() => { - panels.Add(new MatchmakingUserPanel(user) + panels.Add(new PlayerPanel(user) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -215,7 +215,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match [Resolved] private MultiplayerClient client { get; set; } = null!; - public void AcquirePanels(MatchmakingUserPanel[] panels) + public void AcquirePanels(PlayerPanel[] panels) { while (Count < panels.Length) { @@ -259,10 +259,10 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match private partial class PlayerPanelCell : Drawable { - private MatchmakingUserPanel? panel; + private PlayerPanel? panel; private bool isAnimating; - public void AcquirePanel(MatchmakingUserPanel panel) + public void AcquirePanel(PlayerPanel panel) { this.panel = panel; isAnimating = true; @@ -280,7 +280,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match if (panel == null) return; - Size = panel.Horizontal ? MatchmakingUserPanel.SIZE_HORIZONTAL : MatchmakingUserPanel.SIZE_VERTICAL; + Size = panel.Horizontal ? PlayerPanel.SIZE_HORIZONTAL : PlayerPanel.SIZE_VERTICAL; Size *= panel.Scale; var targetPos = getFinalPosition(); diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.ScreenStack.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.ScreenStack.cs index 29a1acb2b8..c1f436e0c9 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.ScreenStack.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.ScreenStack.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match private MultiplayerClient client { get; set; } = null!; private Framework.Screens.ScreenStack screenStack = null!; - private UserPanelOverlay playersList = null!; + private PlayerPanelOverlay playersList = null!; [BackgroundDependencyLoader] private void load() @@ -45,7 +45,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match screenStack = new Framework.Screens.ScreenStack(), } }, - playersList = new UserPanelOverlay + playersList = new PlayerPanelOverlay { DisplayArea = this }, From 7879e091c0bb8e1515271479b315fb47eaafd8f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 14:39:20 +0900 Subject: [PATCH 03/22] Simplify avatar handling in beatmap selection panels --- .../Match/BeatmapSelect/BeatmapSelectPanel.cs | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs index ab89dcd65f..13bf0dea45 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -95,13 +95,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect } } }, - selectionOverlay = new AvatarOverlay - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10 }, - Origin = Anchor.CentreLeft, - }, + selectionOverlay = new AvatarOverlay() } }, new HoverClickSounds(), @@ -358,36 +352,27 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect private partial class AvatarOverlay : CompositeDrawable { - private readonly Dictionary avatars = new Dictionary(); - - private readonly Container avatarContainer; + private readonly Container avatars; private Sample? userAddedSample; private double? lastSamplePlayback; - public new Axes AutoSizeAxes - { - get => base.AutoSizeAxes; - set => base.AutoSizeAxes = value; - } - - public new MarginPadding Padding - { - get => base.Padding; - set => base.Padding = value; - } - public AvatarOverlay() { - InternalChild = avatarContainer = new Container(); + InternalChild = avatars = new Container(); + + Padding = new MarginPadding(5); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; } protected override void LoadComplete() { base.LoadComplete(); - avatarContainer.AutoSizeAxes = AutoSizeAxes; - avatarContainer.RelativeSizeAxes = RelativeSizeAxes; + avatars.RelativeSizeAxes = Axes.X; + avatars.AutoSizeAxes = Axes.Y; } [BackgroundDependencyLoader] @@ -398,7 +383,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public bool AddUser(APIUser user, bool isOwnUser) { - if (avatars.ContainsKey(user.Id)) + if (avatars.Any(a => a.User.Id == user.Id)) return false; var avatar = new SelectionAvatar(user, isOwnUser) @@ -407,7 +392,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect Origin = Anchor.CentreRight, }; - avatarContainer.Add(avatars[user.Id] = avatar); + avatars.Add(avatar); if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > OsuGameBase.SAMPLE_DEBOUNCE_TIME) { @@ -424,11 +409,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public bool RemoveUser(int id) { - if (!avatars.Remove(id, out var avatar)) + if (avatars.SingleOrDefault(a => a.User.Id == id) is not SelectionAvatar avatar) return false; avatar.PopOutAndExpire(); - avatarContainer.ChangeChildDepth(avatar, float.MaxValue); + avatars.ChangeChildDepth(avatar, float.MaxValue); updateLayout(); @@ -443,9 +428,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect double delay = 0; float x = 0; - for (int i = avatarContainer.Count - 1; i >= 0; i--) + for (int i = avatars.Count - 1; i >= 0; i--) { - var avatar = avatarContainer[i]; + var avatar = avatars[i]; if (avatar.Expired) continue; @@ -460,12 +445,15 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public partial class SelectionAvatar : CompositeDrawable { + public APIUser User { get; } + public bool Expired { get; private set; } private readonly Container content; public SelectionAvatar(APIUser user, bool isOwnUser) { + User = user; Size = new Vector2(30); InternalChildren = new Drawable[] From d690477776a53f80fbb840ee05b9f6d3ead50c6b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 14:52:23 +0900 Subject: [PATCH 04/22] Add context menu to show beatmap details --- .../Match/BeatmapSelect/BeatmapSelectPanel.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs index 13bf0dea45..a2f10e05f4 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs @@ -11,7 +11,9 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -20,6 +22,7 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Overlays; @@ -196,7 +199,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect } // TODO: combine following two classes with above implementation for simplicity? - private partial class BeatmapPanel : CompositeDrawable + private partial class BeatmapPanel : CompositeDrawable, IHasContextMenu { public readonly Container OverlayLayer = new Container { RelativeSizeAxes = Axes.Both }; @@ -283,6 +286,27 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect } } + [Resolved] + private BeatmapSetOverlay? beatmapSetOverlay { get; set; } + + public MenuItem[] ContextMenuItems + { + get + { + // this is very weird, but the beatmap may be null while loading because reasons. + if (beatmap == null) + return []; + + return new MenuItem[] + { + new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, () => + { + beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet!.OnlineID); + }), + }; + } + } + private partial class BeatmapPanelContent : CompositeDrawable { private readonly APIBeatmap beatmap; @@ -400,7 +424,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect lastSamplePlayback = Time.Current; } - updateLayout(); + updateAvatarLayout(); avatar.FinishTransforms(); @@ -415,12 +439,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect avatar.PopOutAndExpire(); avatars.ChangeChildDepth(avatar, float.MaxValue); - updateLayout(); + updateAvatarLayout(); return true; } - private void updateLayout() + private void updateAvatarLayout() { const double stagger = 30; const float spacing = 4; From e9063dcf57dc93fdc0bbbd06f843c6e5a9245b80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 15:27:10 +0900 Subject: [PATCH 05/22] Simplify flash layer --- .../Match/BeatmapSelect/BeatmapSelectPanel.cs | 155 +++++++----------- 1 file changed, 63 insertions(+), 92 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs index a2f10e05f4..053c89da3e 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs @@ -44,15 +44,13 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect private readonly BeatmapPanel beatmapPanel; private readonly AvatarOverlay selectionOverlay; private readonly Container border; - private readonly Box flash; + + private readonly Drawable lighting; public bool AllowSelection; public Action? Action; - [Resolved] - private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; - public override bool PropagatePositionalInputSubTree => AllowSelection; public BeatmapSelectPanel(MultiplayerPlaylistItem item) @@ -60,76 +58,64 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect Item = item; Size = SIZE; - InternalChildren = new Drawable[] + InternalChild = scaleContainer = new Container { - scaleContainer = new Container + Masking = true, + CornerRadius = 6, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + new HoverClickSounds(), + new Container { - new Container + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(-border_width), + Child = border = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(-border_width), - Child = border = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = corner_radius + border_width, - Alpha = 0, - Child = new Box { RelativeSizeAxes = Axes.Both }, - } - }, - beatmapPanel = new BeatmapPanel - { - RelativeSizeAxes = Axes.Both, - OverlayLayer = - { - Children = new[] - { - flash = new Box - { - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - } - } - }, - selectionOverlay = new AvatarOverlay() + Masking = true, + CornerRadius = corner_radius + border_width, + Alpha = 0, + Child = new Box { RelativeSizeAxes = Axes.Both }, + } + }, + beatmapPanel = new BeatmapPanel { RelativeSizeAxes = Axes.Both }, + lighting = new Box + { + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + selectionOverlay = new AvatarOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, } - }, - new HoverClickSounds(), + } }; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(BeatmapLookupCache lookupCache) { - base.LoadComplete(); - - beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ContinueWith(b => Schedule(() => + lookupCache.GetBeatmapAsync(Item.BeatmapID).ContinueWith(b => Schedule(() => { var beatmap = b.GetResultSafely()!; - beatmap.StarRating = Item.StarRating; - beatmapPanel.Beatmap = beatmap; })); } public bool AddUser(APIUser user, bool isOwnUser = false) => selectionOverlay.AddUser(user, isOwnUser); - - public bool RemoveUser(int userId) => selectionOverlay.RemoveUser(userId); - - public bool RemoveUser(APIUser user) => RemoveUser(user.Id); + public bool RemoveUser(APIUser user) => selectionOverlay.RemoveUser(user.Id); protected override bool OnHover(HoverEvent e) { - flash.FadeTo(0.2f, 50) - .Then() - .FadeTo(0.1f, 300); + lighting.FadeTo(0.2f, 50) + .Then() + .FadeTo(0.1f, 300); return true; } @@ -138,7 +124,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { base.OnHoverLost(e); - flash.FadeOut(200); + lighting.FadeOut(200); } protected override bool OnMouseDown(MouseDownEvent e) @@ -166,9 +152,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { Action?.Invoke(Item); - flash.FadeTo(0.5f, 50) - .Then() - .FadeTo(0.1f, 400); + lighting.FadeTo(0.5f, 50) + .Then() + .FadeTo(0.1f, 400); return true; } @@ -201,11 +187,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect // TODO: combine following two classes with above implementation for simplicity? private partial class BeatmapPanel : CompositeDrawable, IHasContextMenu { - public readonly Container OverlayLayer = new Container { RelativeSizeAxes = Axes.Both }; - public APIBeatmap? Beatmap { - get => beatmap; set { if (beatmap?.OnlineID == value?.OnlineID) @@ -233,6 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { Masking = true; CornerRadius = 6; + CornerExponent = 10; InternalChildren = new Drawable[] { @@ -254,7 +238,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { RelativeSizeAxes = Axes.Both, }, - OverlayLayer, }; } @@ -383,20 +366,15 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public AvatarOverlay() { - InternalChild = avatars = new Container(); + AutoSizeAxes = Axes.Both; + + InternalChild = avatars = new Container + { + AutoSizeAxes = Axes.X, + Height = SelectionAvatar.AVATAR_SIZE, + }; Padding = new MarginPadding(5); - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - avatars.RelativeSizeAxes = Axes.X; - avatars.AutoSizeAxes = Axes.Y; } [BackgroundDependencyLoader] @@ -410,11 +388,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect if (avatars.Any(a => a.User.Id == user.Id)) return false; - var avatar = new SelectionAvatar(user, isOwnUser) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - }; + var avatar = new SelectionAvatar(user, isOwnUser); avatars.Add(avatar); @@ -469,26 +443,23 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public partial class SelectionAvatar : CompositeDrawable { + public const float AVATAR_SIZE = 30; + public APIUser User { get; } public bool Expired { get; private set; } - private readonly Container content; + private readonly MatchmakingAvatar avatar; public SelectionAvatar(APIUser user, bool isOwnUser) { User = user; - Size = new Vector2(30); + Size = new Vector2(AVATAR_SIZE); - InternalChildren = new Drawable[] + InternalChild = avatar = new MatchmakingAvatar(user, isOwnUser) { - content = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = new MatchmakingAvatar(user, isOwnUser) - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }; } @@ -496,14 +467,14 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { base.LoadComplete(); - content.ScaleTo(0) - .ScaleTo(1, 500, Easing.OutElasticHalf) - .FadeIn(200); + avatar.ScaleTo(0) + .ScaleTo(1, 500, Easing.OutElasticHalf) + .FadeIn(200); } public void PopOutAndExpire() { - content.ScaleTo(0, 400, Easing.OutExpo); + avatar.ScaleTo(0, 400, Easing.OutExpo); this.FadeOut(100).Expire(); Expired = true; From 3af4edf0512290d5f74f10f575a3341f6b586963 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 15:23:06 +0900 Subject: [PATCH 06/22] Remove pointless `TestSceneMatchmakingScreenStack` Should always be using `TestSceneMatchmakingScreen` right? --- .../TestSceneMatchmakingScreenStack.cs | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreenStack.cs diff --git a/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreenStack.cs b/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreenStack.cs deleted file mode 100644 index ba7e27b753..0000000000 --- a/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreenStack.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Multiplayer; -using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; -using osu.Game.Online.Rooms; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.OnlinePlay.Matchmaking.Match; -using osu.Game.Tests.Visual.Multiplayer; - -namespace osu.Game.Tests.Visual.Matchmaking -{ - public partial class TestSceneMatchmakingScreenStack : MultiplayerTestScene - { - private const int user_count = 8; - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("join room", () => - { - var room = CreateDefaultRoom(MatchType.Matchmaking); - room.Playlist = Enumerable.Range(1, 50).Select(i => new PlaylistItem(new MultiplayerPlaylistItem - { - ID = i, - BeatmapID = i, - StarRating = i / 10.0, - })).ToArray(); - - JoinRoom(room); - }); - - WaitForJoined(); - - AddStep("add carousel", () => - { - Child = new ScreenMatchmaking.ScreenStack - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }; - }); - - AddStep("join users", () => - { - var users = Enumerable.Range(1, user_count).Select(i => new MultiplayerRoomUser(i) - { - User = new APIUser - { - Username = $"Player {i}" - } - }).ToArray(); - - foreach (var user in users) - MultiplayerClient.AddUser(user); - }); - } - - [Test] - public void TestChangeStage() - { - for (int round = 1; round <= 2; round++) - { - AddLabel($"Round {round}"); - - int r = round; - changeStage(MatchmakingStage.RoundWarmupTime, state => state.CurrentRound = r); - changeStage(MatchmakingStage.UserBeatmapSelect); - changeStage(MatchmakingStage.ServerBeatmapFinalised, state => - { - MultiplayerPlaylistItem[] beatmaps = Enumerable.Range(1, 8).Select(i => new MultiplayerPlaylistItem - { - ID = i, - BeatmapID = i, - StarRating = i / 10.0, - }).ToArray(); - - state.CandidateItems = beatmaps.Select(b => b.ID).ToArray(); - state.CandidateItem = beatmaps[0].ID; - }, waitTime: 35); - - changeStage(MatchmakingStage.WaitingForClientsBeatmapDownload); - changeStage(MatchmakingStage.GameplayWarmupTime); - changeStage(MatchmakingStage.Gameplay); - changeStage(MatchmakingStage.ResultsDisplaying); - } - - changeStage(MatchmakingStage.Ended, state => - { - int localUserId = API.LocalUser.Value.OnlineID; - - state.Users[localUserId].Placement = 1; - state.Users[localUserId].Rounds[1].Placement = 1; - state.Users[localUserId].Rounds[1].TotalScore = 1; - state.Users[localUserId].Rounds[1].Statistics[HitResult.LargeBonus] = 1; - }); - } - - private void changeStage(MatchmakingStage stage, Action? prepare = null, int waitTime = 5) - { - AddStep($"stage: {stage}", () => MultiplayerClient.MatchmakingChangeStage(stage, prepare).WaitSafely()); - AddWaitStep("wait", waitTime); - } - } -} From 9dc5605d9500d6a10095cd513189c203231ab173 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 16:15:37 +0900 Subject: [PATCH 07/22] Scale active stage larger --- .../Matchmaking/Match/StageDisplay.StageSegment.cs | 10 +++++++++- .../OnlinePlay/Matchmaking/Match/StageDisplay.cs | 7 +++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.StageSegment.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.StageSegment.cs index f97bf9fe68..301cac1437 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.StageSegment.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.StageSegment.cs @@ -42,6 +42,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match private Sample? countdownTickSample; private double? lastSamplePlayback; + private Container mainContent = null!; + public bool Active { get; private set; } public float Progress => progressBar.Width; @@ -49,10 +51,14 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match public StageSegment(int? round, MatchmakingStage stage, LocalisableString displayText) { Round = round; + this.stage = stage; this.displayText = displayText; AutoSizeAxes = Axes.Both; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; } [BackgroundDependencyLoader] @@ -74,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match Icon = FontAwesome.Solid.ArrowRight, Margin = new MarginPadding { Horizontal = 10 } }, - new Container + mainContent = new Container { Masking = true, CornerRadius = 5, @@ -178,6 +184,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match if (wasActive) progressBar.Width = 1; + mainContent.ScaleTo(Active ? 1.3f : 1, 500, Easing.OutQuint); + bool isPreparing = (stage == MatchmakingStage.RoundWarmupTime && roomState.Stage == MatchmakingStage.WaitingForClientsJoin) || (stage == MatchmakingStage.GameplayWarmupTime && roomState.Stage == MatchmakingStage.WaitingForClientsBeatmapDownload) || diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs index db302163a5..419824549b 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs @@ -57,17 +57,16 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match { scroll = new StageScrollContainer { - ScrollbarOverlapsContent = false, ScrollbarVisible = false, ClampExtension = 0, RelativeSizeAxes = Axes.X, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Height = 36, + Height = HEIGHT, Child = flow = new FillFlowContainer { Padding = new MarginPadding { Horizontal = 2000 }, AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, }, }, From 76c304391348cb76d82b96c14e985d66b41f719d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 16:48:56 +0900 Subject: [PATCH 08/22] Improve selection animation border isolation I'm not final on the design, just wanted to split it out into an actual border element rather than an "underlay". --- .../Match/BeatmapSelect/BeatmapSelectGrid.cs | 2 +- .../Match/BeatmapSelect/BeatmapSelectPanel.cs | 107 ++++++++++++------ 2 files changed, 73 insertions(+), 36 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs index 209c6f553a..a784f644ab 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs @@ -330,7 +330,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { rollContainer.ChangeChildDepth(panel, float.MinValue); - panel.ShowBorder(); + panel.ShowChosenBorder(); panel.MoveTo(Vector2.Zero, 1000, Easing.OutExpo) .ScaleTo(1.5f, 1000, Easing.OutExpo); diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs index 053c89da3e..fdb3954535 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; @@ -27,6 +28,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osuTK; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect @@ -35,21 +37,21 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { public static readonly Vector2 SIZE = new Vector2(300, 70); - private const float corner_radius = 6; - private const float border_width = 3; + public bool AllowSelection { get; set; } public readonly MultiplayerPlaylistItem Item; - private readonly Container scaleContainer; - private readonly BeatmapPanel beatmapPanel; - private readonly AvatarOverlay selectionOverlay; - private readonly Container border; + public Action? Action { private get; init; } - private readonly Drawable lighting; + private const float corner_radius = 6; + private const float border_width = 3; - public bool AllowSelection; + private Container scaleContainer = null!; + private BeatmapPanel beatmapPanel = null!; + private AvatarOverlay selectionOverlay = null!; + private Drawable lighting = null!; - public Action? Action; + private Container border = null!; public override bool PropagatePositionalInputSubTree => AllowSelection; @@ -57,49 +59,71 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { Item = item; Size = SIZE; + } + [BackgroundDependencyLoader] + private void load(BeatmapLookupCache lookupCache, OverlayColourProvider colourProvider) + { InternalChild = scaleContainer = new Container { - Masking = true, - CornerRadius = 6, RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, Children = new[] { - new HoverClickSounds(), new Container { + Masking = true, + CornerRadius = corner_radius, + CornerExponent = 10, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(-border_width), - Child = border = new Container + Children = new[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = corner_radius + border_width, - Alpha = 0, - Child = new Box { RelativeSizeAxes = Axes.Both }, + new HoverClickSounds(), + beatmapPanel = new BeatmapPanel { RelativeSizeAxes = Axes.Both }, + lighting = new Box + { + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + selectionOverlay = new AvatarOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + } } }, - beatmapPanel = new BeatmapPanel { RelativeSizeAxes = Axes.Both }, - lighting = new Box + border = new Container { - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, Alpha = 0, + Masking = true, + CornerRadius = corner_radius, + Blending = BlendingParameters.Additive, + CornerExponent = 10, + RelativeSizeAxes = Axes.Both, + BorderThickness = border_width, + BorderColour = colourProvider.Light1, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 40, + Roundness = 300, + Colour = colourProvider.Light3.Opacity(0.1f), + }, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + } }, - selectionOverlay = new AvatarOverlay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - } } }; - } - - [BackgroundDependencyLoader] - private void load(BeatmapLookupCache lookupCache) - { lookupCache.GetBeatmapAsync(Item.BeatmapID).ContinueWith(b => Schedule(() => { var beatmap = b.GetResultSafely()!; @@ -159,9 +183,22 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect return true; } - public void ShowBorder() => border.Show(); + public void ShowChosenBorder() + { + border.FadeTo(1, 1000, Easing.OutQuint); + } - public void HideBorder() => border.Hide(); + public void ShowBorder() + { + border.FadeTo(1, 80, Easing.OutQuint) + .Then() + .FadeTo(0.7f, 800, Easing.OutQuint); + } + + public void HideBorder() + { + border.FadeOut(500, Easing.OutQuint); + } public void FadeInAndEnterFromBelow(double duration = 500, double delay = 0, float distance = 200) { From badeb24d566e263e56c41a55f1feda1bf14653ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 16:53:22 +0900 Subject: [PATCH 09/22] Change beatmap in selection panels to always be non-null --- .../Match/BeatmapSelect/BeatmapSelectPanel.cs | 65 ++++++------------- 1 file changed, 21 insertions(+), 44 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs index fdb3954535..01ffe86139 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs @@ -47,11 +47,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect private const float border_width = 3; private Container scaleContainer = null!; - private BeatmapPanel beatmapPanel = null!; private AvatarOverlay selectionOverlay = null!; private Drawable lighting = null!; private Container border = null!; + private Container mainContent = null!; public override bool PropagatePositionalInputSubTree => AllowSelection; @@ -71,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect Origin = Anchor.Centre, Children = new[] { - new Container + mainContent = new Container { Masking = true, CornerRadius = corner_radius, @@ -80,7 +80,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect Children = new[] { new HoverClickSounds(), - beatmapPanel = new BeatmapPanel { RelativeSizeAxes = Axes.Both }, lighting = new Box { Blending = BlendingParameters.Additive, @@ -128,7 +127,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { var beatmap = b.GetResultSafely()!; beatmap.StarRating = Item.StarRating; - beatmapPanel.Beatmap = beatmap; + + mainContent.Add(new BeatmapPanel(beatmap) + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both + }); })); } @@ -224,26 +228,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect // TODO: combine following two classes with above implementation for simplicity? private partial class BeatmapPanel : CompositeDrawable, IHasContextMenu { - public APIBeatmap? Beatmap - { - set - { - if (beatmap?.OnlineID == value?.OnlineID) - return; - - beatmap = value; - - if (IsLoaded) - updateContent(); - } - } - - private APIBeatmap? beatmap; + private readonly APIBeatmap beatmap; private Container content = null!; private UpdateableOnlineBeatmapSetCover cover = null!; - public BeatmapPanel(APIBeatmap? beatmap = null) + public BeatmapPanel(APIBeatmap beatmap) { this.beatmap = beatmap; } @@ -291,41 +281,28 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect foreach (var child in content.Children) child.FadeOut(300).Expire(); - cover.OnlineInfo = beatmap?.BeatmapSet; + cover.OnlineInfo = beatmap.BeatmapSet; - if (beatmap != null) + var panelContent = new BeatmapPanelContent(beatmap) { - var panelContent = new BeatmapPanelContent(beatmap) - { - RelativeSizeAxes = Axes.Both, - }; + RelativeSizeAxes = Axes.Both, + }; - content.Add(panelContent); + content.Add(panelContent); - panelContent.FadeInFromZero(300); - } + panelContent.FadeInFromZero(300); } [Resolved] private BeatmapSetOverlay? beatmapSetOverlay { get; set; } - public MenuItem[] ContextMenuItems + public MenuItem[] ContextMenuItems => new MenuItem[] { - get + new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, () => { - // this is very weird, but the beatmap may be null while loading because reasons. - if (beatmap == null) - return []; - - return new MenuItem[] - { - new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, () => - { - beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet!.OnlineID); - }), - }; - } - } + beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet!.OnlineID); + }), + }; private partial class BeatmapPanelContent : CompositeDrawable { From da80b61f3888869a01d5c2ecb68f69511fdca5ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 18:08:47 +0900 Subject: [PATCH 10/22] Allow visibly disabling the "go to beatmap" button Easiest way to make this work without rewriting the layout logic. I think it makes sense to have the button still exist there but not be usable on certain screens. --- .../Cards/Buttons/GoToBeatmapButton.cs | 32 ++++++++++++++++--- .../Cards/CollapsibleButtonContainer.cs | 22 +++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs index d2c077d010..3d732b6683 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs @@ -16,10 +16,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private readonly Bindable state = new Bindable(); private readonly APIBeatmapSet beatmapSet; + private readonly bool allowNavigationToBeatmap; - public GoToBeatmapButton(APIBeatmapSet beatmapSet) + public GoToBeatmapButton(APIBeatmapSet beatmapSet, bool allowNavigationToBeatmap) { this.beatmapSet = beatmapSet; + this.allowNavigationToBeatmap = allowNavigationToBeatmap; } [BackgroundDependencyLoader(true)] @@ -27,7 +29,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { Action = () => game?.PresentBeatmap(beatmapSet); Icon.Icon = FontAwesome.Solid.AngleDoubleRight; - TooltipText = "Go to beatmap"; } protected override void LoadComplete() @@ -40,8 +41,31 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private void updateState() { - Enabled.Value = state.Value == DownloadState.LocallyAvailable; - this.FadeTo(Enabled.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + bool available = state.Value == DownloadState.LocallyAvailable; + Enabled.Value = allowNavigationToBeatmap && available; + + float alpha; + + if (available && allowNavigationToBeatmap) + { + TooltipText = "Go to beatmap"; + Enabled.Value = true; + alpha = 1f; + } + else if (available) + { + TooltipText = string.Empty; + Enabled.Value = false; + alpha = 0.3f; + } + else + { + TooltipText = string.Empty; + Enabled.Value = false; + alpha = 0; + } + + this.FadeTo(alpha, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs index 56d405ce3c..8283d97817 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs @@ -30,7 +30,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards set { buttonsExpandedWidth = value; - buttonArea.Width = value; if (IsLoaded) updateState(); } @@ -67,7 +66,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public CollapsibleButtonContainer(APIBeatmapSet beatmapSet) + public CollapsibleButtonContainer(APIBeatmapSet beatmapSet, bool allowNavigationToBeatmap = true) { downloadTracker = new BeatmapDownloadTracker(beatmapSet); @@ -116,14 +115,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, Height = 0.5f, }, - new GoToBeatmapButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State }, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - } } } }, @@ -152,6 +143,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }; + + buttons.Add(new GoToBeatmapButton(beatmapSet, allowNavigationToBeatmap) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State }, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + }); } protected override void LoadComplete() @@ -165,6 +165,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() { + buttonArea.Width = buttonsExpandedWidth; + float buttonAreaWidth = ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth; float mainAreaWidth = Width - buttonAreaWidth; From 0a17a3c4edc6e879f05c696fb8b1968379cb5682 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 18:09:05 +0900 Subject: [PATCH 11/22] Use `BeatmapCard` for matchmaking beatmap display --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 4 +- .../BeatmapSelect/BeatmapCardMatchmaking.cs | 354 ++++++++++++++++++ .../Match/BeatmapSelect/BeatmapSelectGrid.cs | 1 - .../Match/BeatmapSelect/BeatmapSelectPanel.cs | 176 +-------- 4 files changed, 365 insertions(+), 170 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 54f8d656fe..4c0466fa04 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -24,7 +24,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards public const float TRANSITION_DURATION = 340; public const float CORNER_RADIUS = 8; - protected const float WIDTH = 345; + public const float WIDTH = 345; public IBindable Expanded { get; } @@ -77,7 +77,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards containingInputManager = GetContainingInputManager(); - Action = () => + Action ??= () => { if (containingInputManager?.CurrentState.Keyboard.ShiftPressed == true) { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs new file mode 100644 index 0000000000..c9df4610f9 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs @@ -0,0 +1,354 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Beatmaps.Drawables.Cards.Statistics; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Resources.Localisation.Web; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect +{ + public partial class BeatmapCardMatchmaking : BeatmapCard + { + private readonly APIBeatmap beatmap; + + protected override Drawable IdleContent => idleBottomContent; + protected override Drawable DownloadInProgressContent => downloadProgressBar; + + public const float HEIGHT = 80; + + [Cached] + private readonly BeatmapCardContent content; + + private BeatmapCardThumbnail thumbnail = null!; + private CollapsibleButtonContainer buttonContainer = null!; + + private FillFlowContainer statisticsContainer = null!; + + private FillFlowContainer idleBottomContent = null!; + private BeatmapCardDownloadProgressBar downloadProgressBar = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public BeatmapCardMatchmaking(APIBeatmap beatmap) + : base(beatmap.BeatmapSet!, false) + { + this.beatmap = beatmap; + content = new BeatmapCardContent(HEIGHT); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Width = WIDTH; + Height = HEIGHT; + + FillFlowContainer leftIconArea = null!; + FillFlowContainer titleBadgeArea = null!; + GridContainer artistContainer = null!; + + Child = content.With(c => + { + c.MainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet) + { + Name = @"Left (icon) area", + Size = new Vector2(HEIGHT), + Padding = new MarginPadding { Right = CORNER_RADIUS }, + Child = leftIconArea = new FillFlowContainer + { + Margin = new MarginPadding(4), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(1) + } + }, + buttonContainer = new CollapsibleButtonContainer(BeatmapSet, allowNavigationToBeatmap: false) + { + X = HEIGHT - CORNER_RADIUS, + Width = WIDTH - HEIGHT + CORNER_RADIUS, + FavouriteState = { BindTarget = FavouriteState }, + ButtonsCollapsedWidth = 0, + ButtonsExpandedWidth = 24, + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new TruncatingSpriteText + { + Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), + Font = OsuFont.Default.With(size: 18f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + }, + titleBadgeArea = new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + } + } + }, + artistContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new TruncatingSpriteText + { + Text = createArtistText(), + Font = OsuFont.Default.With(size: 14f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + }, + Empty() + }, + } + }, + new LinkFlowContainer(s => + { + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.Margin = new MarginPadding { Top = 1 }; + d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); + d.AddUserLink(BeatmapSet.Author); + }), + } + }, + new Container + { + Name = @"Bottom content", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Children = new Drawable[] + { + idleBottomContent = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2), + AlwaysPresent = true, + Children = new Drawable[] + { + statisticsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(8, 0), + Alpha = 0, + AlwaysPresent = true, + ChildrenEnumerable = createStatistics() + }, + new Container + { + Masking = true, + CornerRadius = CORNER_RADIUS, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = colours.ForStarDifficulty(beatmap.StarRating).Darken(0.8f), + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Padding = new MarginPadding(2), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4, 0), + Children = new Drawable[] + { + new StarRatingDisplay(new StarDifficulty(beatmap.StarRating, 0), StarRatingDisplaySize.Small, animated: true) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Scale = new Vector2(0.875f), + }, + new TruncatingSpriteText + { + Text = beatmap.DifficultyName, + Font = OsuFont.Style.Caption2.With(weight: FontWeight.Bold), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + } + }, + } + }, + } + }, + downloadProgressBar = new BeatmapCardDownloadProgressBar + { + RelativeSizeAxes = Axes.X, + Height = 5, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { BindTarget = DownloadTracker.State }, + Progress = { BindTarget = DownloadTracker.Progress } + } + } + } + } + } + } + }; + c.Expanded.BindTarget = Expanded; + }); + + if (BeatmapSet.HasVideo) + leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) }); + + if (BeatmapSet.HasStoryboard) + leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) }); + + if (BeatmapSet.FeaturedInSpotlight) + { + titleBadgeArea.Add(new SpotlightBeatmapBadge + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 4 } + }); + } + + if (BeatmapSet.HasExplicitContent) + { + titleBadgeArea.Add(new ExplicitContentBeatmapBadge + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 4 } + }); + } + + if (BeatmapSet.TrackId != null) + { + artistContainer.Content[0][1] = new FeaturedArtistBeatmapBadge + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 4 } + }; + } + } + + private LocalisableString createArtistText() + { + var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist); + return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); + } + + private IEnumerable createStatistics() + { + var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet); + if (hypesStatistic != null) + yield return hypesStatistic; + + var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet); + if (nominationsStatistic != null) + yield return nominationsStatistic; + + yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState }; + yield return new PlayCountStatistic(BeatmapSet); + + var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet); + if (dateStatistic != null) + yield return dateStatistic; + } + + protected override void UpdateState() + { + base.UpdateState(); + + bool showDetails = IsHovered; + + buttonContainer.ShowDetails.Value = showDetails; + thumbnail.Dimmed.Value = showDetails; + + statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + } + + public override MenuItem[] ContextMenuItems + { + get + { + var items = base.ContextMenuItems.ToList(); + + foreach (var button in buttonContainer.Buttons) + { + if (button.Enabled.Value) + items.Add(new OsuMenuItem(button.TooltipText.ToSentence(), MenuItemType.Standard, () => button.TriggerClick())); + } + + return items.ToArray(); + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs index a784f644ab..4d19890993 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectGrid.cs @@ -110,7 +110,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { var panel = panelLookup[item.ID] = new BeatmapSelectPanel(item) { - Size = new Vector2(300, 70), AllowSelection = allowSelection, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs index 01ffe86139..3266e39905 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapSelectPanel.cs @@ -9,21 +9,12 @@ using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Localisation; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; +using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Database; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Overlays; @@ -35,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { public partial class BeatmapSelectPanel : Container { - public static readonly Vector2 SIZE = new Vector2(300, 70); + public static readonly Vector2 SIZE = new Vector2(BeatmapCard.WIDTH, BeatmapCardNormal.HEIGHT); public bool AllowSelection { get; set; } @@ -43,7 +34,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect public Action? Action { private get; init; } - private const float corner_radius = 6; private const float border_width = 3; private Container scaleContainer = null!; @@ -74,12 +64,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect mainContent = new Container { Masking = true, - CornerRadius = corner_radius, + CornerRadius = BeatmapCard.CORNER_RADIUS, CornerExponent = 10, RelativeSizeAxes = Axes.Both, Children = new[] { - new HoverClickSounds(), lighting = new Box { Blending = BlendingParameters.Additive, @@ -97,9 +86,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect { Alpha = 0, Masking = true, - CornerRadius = corner_radius, - Blending = BlendingParameters.Additive, + CornerRadius = BeatmapCard.CORNER_RADIUS, CornerExponent = 10, + Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, BorderThickness = border_width, BorderColour = colourProvider.Light1, @@ -128,10 +117,10 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect var beatmap = b.GetResultSafely()!; beatmap.StarRating = Item.StarRating; - mainContent.Add(new BeatmapPanel(beatmap) + mainContent.Add(new BeatmapCardMatchmaking(beatmap) { Depth = float.MaxValue, - RelativeSizeAxes = Axes.Both + Action = () => Action?.Invoke(Item), }); })); } @@ -178,13 +167,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect protected override bool OnClick(ClickEvent e) { - Action?.Invoke(Item); - lighting.FadeTo(0.5f, 50) .Then() .FadeTo(0.1f, 400); - return true; + // pass through to let the beatmap card handle actual click. + return false; } public void ShowChosenBorder() @@ -225,152 +213,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect this.Delay(delay + duration).FadeOut().Expire(); } - // TODO: combine following two classes with above implementation for simplicity? - private partial class BeatmapPanel : CompositeDrawable, IHasContextMenu - { - private readonly APIBeatmap beatmap; - - private Container content = null!; - private UpdateableOnlineBeatmapSetCover cover = null!; - - public BeatmapPanel(APIBeatmap beatmap) - { - this.beatmap = beatmap; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Masking = true; - CornerRadius = 6; - CornerExponent = 10; - - InternalChildren = new Drawable[] - { - cover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.Card, timeBeforeLoad: 0, timeBeforeUnload: 10000) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal( - colourProvider.Background4.Opacity(0.7f), - colourProvider.Background4.Opacity(0.4f) - ) - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateContent(); - FinishTransforms(true); - } - - private void updateContent() - { - foreach (var child in content.Children) - child.FadeOut(300).Expire(); - - cover.OnlineInfo = beatmap.BeatmapSet; - - var panelContent = new BeatmapPanelContent(beatmap) - { - RelativeSizeAxes = Axes.Both, - }; - - content.Add(panelContent); - - panelContent.FadeInFromZero(300); - } - - [Resolved] - private BeatmapSetOverlay? beatmapSetOverlay { get; set; } - - public MenuItem[] ContextMenuItems => new MenuItem[] - { - new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, () => - { - beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet!.OnlineID); - }), - }; - - private partial class BeatmapPanelContent : CompositeDrawable - { - private readonly APIBeatmap beatmap; - - public BeatmapPanelContent(APIBeatmap beatmap) - { - this.beatmap = beatmap; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Padding = new MarginPadding { Horizontal = 12 }, - Children = new Drawable[] - { - new TruncatingSpriteText - { - Text = new RomanisableString(beatmap.Metadata.TitleUnicode, beatmap.Metadata.TitleUnicode), - Font = OsuFont.Default.With(size: 19, weight: FontWeight.SemiBold), - RelativeSizeAxes = Axes.X, - }, - new TextFlowContainer(s => - { - s.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold); - }).With(d => - { - d.RelativeSizeAxes = Axes.X; - d.AutoSizeAxes = Axes.Y; - d.AddText("by "); - d.AddText(new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)); - }), - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Top = 6 }, - Spacing = new Vector2(4), - Children = new Drawable[] - { - new StarRatingDisplay(new StarDifficulty(beatmap.StarRating, 0), StarRatingDisplaySize.Small) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - new TruncatingSpriteText - { - Text = beatmap.DifficultyName, - Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } - }, - }, - }; - } - } - } - private partial class AvatarOverlay : CompositeDrawable { private readonly Container avatars; From b7435062072d53f4634bc8d95f3a6ad5b9bd86a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 18:26:09 +0900 Subject: [PATCH 12/22] Keep panel backgrounds loaded --- .../Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs | 4 ++-- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs | 4 ++-- .../Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs | 4 ++-- .../Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs index deb56bb281..a57f3e7ce7 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs @@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public BeatmapCardContentBackground(IBeatmapSetOnlineInfo onlineInfo) + public BeatmapCardContentBackground(IBeatmapSetOnlineInfo onlineInfo, bool keepLoaded = false) { InternalChildren = new Drawable[] { @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.Both, }, - cover = new DelayedLoadUnloadWrapper(() => createCover(onlineInfo), 500, 500) + cover = new DelayedLoadUnloadWrapper(() => createCover(onlineInfo), keepLoaded ? 0 : 500, keepLoaded ? double.MaxValue : 1000) { RelativeSizeAxes = Axes.Both, Colour = Colour4.Transparent diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 1f6f638618..4a7054588e 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -35,11 +35,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public BeatmapCardThumbnail(IBeatmapSetInfo beatmapSetInfo, IBeatmapSetOnlineInfo onlineInfo) + public BeatmapCardThumbnail(IBeatmapSetInfo beatmapSetInfo, IBeatmapSetOnlineInfo onlineInfo, bool keepLoaded = false) { InternalChildren = new Drawable[] { - new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) + new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List, keepLoaded ? 0 : 500, keepLoaded ? double.MaxValue : 1000) { RelativeSizeAxes = Axes.Both, OnlineInfo = onlineInfo diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs index 8283d97817..8262e787d8 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public CollapsibleButtonContainer(APIBeatmapSet beatmapSet, bool allowNavigationToBeatmap = true) + public CollapsibleButtonContainer(APIBeatmapSet beatmapSet, bool allowNavigationToBeatmap = true, bool keepBackgroundLoaded = false) { downloadTracker = new BeatmapDownloadTracker(beatmapSet); @@ -126,7 +126,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Masking = true, Children = new Drawable[] { - new BeatmapCardContentBackground(beatmapSet) + new BeatmapCardContentBackground(beatmapSet, keepBackgroundLoaded) { RelativeSizeAxes = Axes.Both, Dimmed = { BindTarget = ShowDetails } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs index c9df4610f9..8fbf8491d6 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/BeatmapSelect/BeatmapCardMatchmaking.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet) + thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet, keepLoaded: true) { Name = @"Left (icon) area", Size = new Vector2(HEIGHT), @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect Spacing = new Vector2(1) } }, - buttonContainer = new CollapsibleButtonContainer(BeatmapSet, allowNavigationToBeatmap: false) + buttonContainer = new CollapsibleButtonContainer(BeatmapSet, allowNavigationToBeatmap: false, keepBackgroundLoaded: true) { X = HEIGHT - CORNER_RADIUS, Width = WIDTH - HEIGHT + CORNER_RADIUS, From 0f8d8780d389b8428937e47618be67a2e250d785 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Sep 2025 18:09:14 +0900 Subject: [PATCH 13/22] Adjust stage display animation to linger for longer --- osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs index 419824549b..e383df71c9 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/StageDisplay.cs @@ -225,8 +225,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match round = value.Value; - this.ScaleTo(6, 500, Easing.OutQuart) - .MoveToY(-300, 500, Easing.OutQuart) + this.ScaleTo(6, 1000, Easing.OutPow10) + .MoveToY(-300, 1000, Easing.OutPow10) .Then() .MoveToY(0, 500, Easing.InQuart) .ScaleTo(1, 500, Easing.InQuart); From 55ef22139041172d1e146ce7e98d77918aa7b507 Mon Sep 17 00:00:00 2001 From: tadatomix Date: Sun, 28 Sep 2025 22:49:45 +0300 Subject: [PATCH 14/22] Add a separate panel for `RankAchieved` group --- .../Screens/SelectV2/PanelGroupRankDisplay.cs | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs diff --git a/osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs b/osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs new file mode 100644 index 0000000000..c174ccaf97 --- /dev/null +++ b/osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs @@ -0,0 +1,225 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; +using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class PanelGroupRankDisplay : Panel + { + public const float HEIGHT = PanelGroup.HEIGHT; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private Drawable iconContainer = null!; + private Box backgroundBorder = null!; + private Box contentBackground = null!; + private OsuSpriteText starRatingText = null!; + private CircularContainer countPill = null!; + private OsuSpriteText countText = null!; + private TrianglesV2 triangles = null!; + private Box glow = null!; + + [BackgroundDependencyLoader] + private void load() + { + Height = PanelGroup.HEIGHT; + + Icon = iconContainer = new Container + { + AlwaysPresent = true, + RelativeSizeAxes = Axes.Y, + Alpha = 0f, + Child = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.ChevronDown, + Size = new Vector2(12), + }, + }; + + Background = backgroundBorder = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Highlight1, + }; + + AccentColour = colourProvider.Highlight1; + Content.Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + triangles = new TrianglesV2 + { + RelativeSizeAxes = Axes.Both, + Thickness = 0.02f, + SpawnRatio = 0.6f, + Colour = ColourInfo.GradientHorizontal(colourProvider.Background6, colourProvider.Background5) + }, + glow = new Box + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Colour = ColourInfo.GradientHorizontal(colourProvider.Highlight1, colourProvider.Highlight1.Opacity(0f)), + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10f, 0f), + Margin = new MarginPadding { Left = 10f }, + Children = new Drawable[] + { + starRatingText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + UseFullGlyphHeight = false, + Font = OsuFont.Style.Heading2, + } + } + }, + countPill = new CircularContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(50f, 14f), + Margin = new MarginPadding { Right = 30f }, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + countText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Style.Caption1.With(weight: FontWeight.Bold), + UseFullGlyphHeight = false, + } + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Expanded.BindValueChanged(_ => onExpanded(), true); + } + + private Color4 rankColour; + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + Debug.Assert(Item != null); + + var group = (RankDisplayGroupDefinition)Item.Model; + ScoreRank rank = group.Rank; + + rankColour = OsuColour.ForRank(rank); + + AccentColour = rankColour; + backgroundBorder.Colour = rankColour; + contentBackground.Colour = rankColour.Darken(1f); + glow.Colour = ColourInfo.GradientHorizontal(rankColour, rankColour.Opacity(0f)); + + switch (rank) + { + case ScoreRank.SH: + case ScoreRank.XH: + starRatingText.Colour = ColourInfo.GradientVertical(Color4.White, Color4Extensions.FromHex("afdff0")); + iconContainer.Colour = colourProvider.Background5; + break; + + case ScoreRank.X: + case ScoreRank.S: + starRatingText.Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ffe7a8"), Color4Extensions.FromHex(@"ffb800")); + iconContainer.Colour = colourProvider.Background5; + break; + + case ScoreRank.F: + starRatingText.Colour = Color4Extensions.FromHex(@"CC3333"); + iconContainer.Colour = colourProvider.Content1; + break; + + default: + starRatingText.Colour = Color4.White; + iconContainer.Colour = colourProvider.Background5; + break; + } + + starRatingText.Text = group.Title; + + ColourInfo colour = ColourInfo.GradientHorizontal(rankColour.Darken(0.6f), rankColour.Darken(0.8f)); + + triangles.Colour = colour; + + countText.Text = Item.NestedItemCount.ToLocalisableString(@"N0"); + + onExpanded(); + } + + private void onExpanded() + { + const float duration = 500; + + iconContainer.ResizeWidthTo(Expanded.Value ? 20f : 5f, duration, Easing.OutQuint); + iconContainer.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); + + glow.FadeTo(Expanded.Value ? 0.4f : 0, duration, Easing.OutQuint); + } + + protected override void Update() + { + base.Update(); + + // Move the count pill in the opposite direction to keep it pinned to the screen regardless of the X position of TopLevelContent. + countPill.X = -TopLevelContent.X; + } + + public override MenuItem[] ContextMenuItems + { + get + { + if (Item == null) + return Array.Empty(); + + return new MenuItem[] + { + new OsuMenuItem(Expanded.Value ? WebCommonStrings.ButtonsCollapse.ToSentence() : WebCommonStrings.ButtonsExpand.ToSentence(), MenuItemType.Highlighted, () => TriggerClick()) + }; + } + } + } +} From cf20bbbf80cae223fb8b1b44be6b8248946d5d5b Mon Sep 17 00:00:00 2001 From: tadatomix Date: Sun, 28 Sep 2025 22:55:22 +0300 Subject: [PATCH 15/22] Add a new `Rank Achieved` panel to BeatmapCarousel.cs --- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 52d5989c8f..679fec76f2 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -788,9 +788,11 @@ namespace osu.Game.Screens.SelectV2 private readonly DrawablePool setPanelPool = new DrawablePool(100); private readonly DrawablePool groupPanelPool = new DrawablePool(100); private readonly DrawablePool starsGroupPanelPool = new DrawablePool(11); + private readonly DrawablePool ranksGroupPanelPool = new DrawablePool(11); private void setupPools() { + AddInternal(ranksGroupPanelPool); AddInternal(starsGroupPanelPool); AddInternal(groupPanelPool); AddInternal(beatmapPanelPool); @@ -829,6 +831,9 @@ namespace osu.Game.Screens.SelectV2 case StarDifficultyGroupDefinition: return starsGroupPanelPool.Get(); + case RankDisplayGroupDefinition: + return ranksGroupPanelPool.Get(); + case GroupDefinition: return groupPanelPool.Get(); @@ -1085,6 +1090,11 @@ namespace osu.Game.Screens.SelectV2 /// public record StarDifficultyGroupDefinition(int Order, string Title, StarDifficulty Difficulty) : GroupDefinition(Order, Title); + /// + /// Defines a grouping header for a set of carousel items grouped by achieved rank. + /// + public record RankDisplayGroupDefinition(int Order, string Title, ScoreRank Rank) : GroupDefinition(Order, Title); + /// /// Used to represent a portion of a under a . /// The purpose of this model is to support splitting beatmap sets apart when the active grouping mode demands it. From 33791318fe22316b933ef821c54c5d9c8a323853 Mon Sep 17 00:00:00 2001 From: tadatomix Date: Sun, 28 Sep 2025 22:56:32 +0300 Subject: [PATCH 16/22] Call a new panel style, when `Rank Achieved` grouping is picked --- osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 69f5596578..f2159d63f5 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -433,7 +433,7 @@ namespace osu.Game.Screens.SelectV2 private IEnumerable defineGroupByRankAchieved(BeatmapInfo beatmap, IReadOnlyDictionary topRankMapping) { if (topRankMapping.TryGetValue(beatmap.ID, out var rank)) - return new GroupDefinition(-(int)rank, rank.GetDescription()).Yield(); + return new RankDisplayGroupDefinition(-(int)rank, rank.GetDescription(), rank).Yield(); return new GroupDefinition(int.MaxValue, "Unplayed").Yield(); } From 6ac4f482ee2151998f89b4fa7e7f16666e15583d Mon Sep 17 00:00:00 2001 From: tadatomix Date: Sun, 28 Sep 2025 23:10:32 +0300 Subject: [PATCH 17/22] Add a new test for `Rank Achieved` panels --- .../SongSelectV2/TestScenePanelGroup.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelGroup.cs b/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelGroup.cs index 3b9a07437a..e6a58136fa 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelGroup.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelGroup.cs @@ -3,12 +3,14 @@ using System; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Graphics.Carousel; using osu.Game.Graphics.Cursor; +using osu.Game.Scoring; using osu.Game.Screens.SelectV2; using osu.Game.Tests.Visual.UserInterface; using osuTK; @@ -86,6 +88,64 @@ namespace osu.Game.Tests.Visual.SongSelectV2 } } + [Test] + public void TestRanks() + { + for (int i = -1; i <= 7; i++) + { + ScoreRank rank = (ScoreRank)i; + + AddStep($"display rank {rank}", () => + { + ContentContainer.Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Aquamarine)) + }, + Child = new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 5f), + Children = new[] + { + new PanelGroupRankDisplay + { + Item = new CarouselItem(new RankDisplayGroupDefinition(0, $"{rank.GetDescription()}", rank)) + }, + new PanelGroupRankDisplay + { + Item = new CarouselItem(new RankDisplayGroupDefinition(1, $"{rank.GetDescription()}", rank)), + KeyboardSelected = { Value = true }, + }, + new PanelGroupRankDisplay + { + Item = new CarouselItem(new RankDisplayGroupDefinition(2, $"{rank.GetDescription()}", rank)), + Expanded = { Value = true }, + }, + new PanelGroupRankDisplay + { + Item = new CarouselItem(new RankDisplayGroupDefinition(3, $"{rank.GetDescription()}", rank)), + Expanded = { Value = true }, + KeyboardSelected = { Value = true }, + }, + }, + } + } + }; + }); + } + } + protected override Drawable CreateContent() { return new OsuContextMenuContainer From 9dc79e6f0d9924e42b0bc2c420e2abea0ec9bc4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Sep 2025 09:31:50 +0200 Subject: [PATCH 18/22] Avoid passing the same thing three times --- .../Visual/SongSelectV2/TestScenePanelGroup.cs | 9 ++++----- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 3 ++- .../Screens/SelectV2/BeatmapCarouselFilterGrouping.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelGroup.cs b/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelGroup.cs index e6a58136fa..f678ec372a 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelGroup.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelGroup.cs @@ -3,7 +3,6 @@ using System; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -120,21 +119,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { new PanelGroupRankDisplay { - Item = new CarouselItem(new RankDisplayGroupDefinition(0, $"{rank.GetDescription()}", rank)) + Item = new CarouselItem(new RankDisplayGroupDefinition(rank)) }, new PanelGroupRankDisplay { - Item = new CarouselItem(new RankDisplayGroupDefinition(1, $"{rank.GetDescription()}", rank)), + Item = new CarouselItem(new RankDisplayGroupDefinition(rank)), KeyboardSelected = { Value = true }, }, new PanelGroupRankDisplay { - Item = new CarouselItem(new RankDisplayGroupDefinition(2, $"{rank.GetDescription()}", rank)), + Item = new CarouselItem(new RankDisplayGroupDefinition(rank)), Expanded = { Value = true }, }, new PanelGroupRankDisplay { - Item = new CarouselItem(new RankDisplayGroupDefinition(3, $"{rank.GetDescription()}", rank)), + Item = new CarouselItem(new RankDisplayGroupDefinition(rank)), Expanded = { Value = true }, KeyboardSelected = { Value = true }, }, diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 679fec76f2..135187dc08 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -13,6 +13,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -1093,7 +1094,7 @@ namespace osu.Game.Screens.SelectV2 /// /// Defines a grouping header for a set of carousel items grouped by achieved rank. /// - public record RankDisplayGroupDefinition(int Order, string Title, ScoreRank Rank) : GroupDefinition(Order, Title); + public record RankDisplayGroupDefinition(ScoreRank Rank) : GroupDefinition(-(int)Rank, Rank.GetDescription()); /// /// Used to represent a portion of a under a . diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index f2159d63f5..37ea7b7497 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -433,7 +433,7 @@ namespace osu.Game.Screens.SelectV2 private IEnumerable defineGroupByRankAchieved(BeatmapInfo beatmap, IReadOnlyDictionary topRankMapping) { if (topRankMapping.TryGetValue(beatmap.ID, out var rank)) - return new RankDisplayGroupDefinition(-(int)rank, rank.GetDescription(), rank).Yield(); + return new RankDisplayGroupDefinition(rank).Yield(); return new GroupDefinition(int.MaxValue, "Unplayed").Yield(); } From fd412618dba7399b1778b0902b73ffbae21a2399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Sep 2025 09:34:40 +0200 Subject: [PATCH 19/22] Adjust initial pool size for group rank displays 11 is excessive. There can ever be at most 9 of these panels, ever, because there are at most 9 possible letter grades at this time (F, D, C, B, A, S, S+, SS, SS+). --- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 135187dc08..d2b18b2f33 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -789,7 +789,7 @@ namespace osu.Game.Screens.SelectV2 private readonly DrawablePool setPanelPool = new DrawablePool(100); private readonly DrawablePool groupPanelPool = new DrawablePool(100); private readonly DrawablePool starsGroupPanelPool = new DrawablePool(11); - private readonly DrawablePool ranksGroupPanelPool = new DrawablePool(11); + private readonly DrawablePool ranksGroupPanelPool = new DrawablePool(9); private void setupPools() { From 47b6b70cae56767ddcec0b7aab2dd58a64e91a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Sep 2025 09:44:05 +0200 Subject: [PATCH 20/22] Avoid duplicating rank name colour constants --- osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs b/osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs index c174ccaf97..95e8b5f43b 100644 --- a/osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs +++ b/osu.Game/Screens/SelectV2/PanelGroupRankDisplay.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Scoring; using osuTK; @@ -158,18 +159,18 @@ namespace osu.Game.Screens.SelectV2 { case ScoreRank.SH: case ScoreRank.XH: - starRatingText.Colour = ColourInfo.GradientVertical(Color4.White, Color4Extensions.FromHex("afdff0")); + starRatingText.Colour = DrawableRank.GetRankNameColour(rank); iconContainer.Colour = colourProvider.Background5; break; case ScoreRank.X: case ScoreRank.S: - starRatingText.Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ffe7a8"), Color4Extensions.FromHex(@"ffb800")); + starRatingText.Colour = DrawableRank.GetRankNameColour(rank); iconContainer.Colour = colourProvider.Background5; break; case ScoreRank.F: - starRatingText.Colour = Color4Extensions.FromHex(@"CC3333"); + starRatingText.Colour = DrawableRank.GetRankNameColour(rank); iconContainer.Colour = colourProvider.Content1; break; From 3f5b71fdc3859dd1d415c9da75c2552cc47e0fd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Sep 2025 17:31:45 +0900 Subject: [PATCH 21/22] Always explicitly assign the action for beatmap cards --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 36 ++++++++++--------- .../Drawables/Cards/BeatmapCardExtra.cs | 2 ++ .../Drawables/Cards/BeatmapCardNano.cs | 2 ++ .../Drawables/Cards/BeatmapCardNormal.cs | 2 ++ 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 4c0466fa04..8cd0ac965a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -77,25 +77,27 @@ namespace osu.Game.Beatmaps.Drawables.Cards containingInputManager = GetContainingInputManager(); - Action ??= () => - { - if (containingInputManager?.CurrentState.Keyboard.ShiftPressed == true) - { - switch (DownloadTracker.State.Value) - { - case DownloadState.NotDownloaded: - if (!BeatmapSet.Availability.DownloadDisabled) - beatmaps?.Download(BeatmapSet, preferNoVideo.Value); - break; + if (Action == null) + throw new InvalidOperationException($"An action should be assigned to this {nameof(BeatmapCard)}. To use the default, assign {nameof(DefaultAction)}."); + } - case DownloadState.LocallyAvailable: - game?.PresentBeatmap(BeatmapSet); - break; - } + protected void DefaultAction() + { + if (containingInputManager?.CurrentState.Keyboard.ShiftPressed == true) + { + switch (DownloadTracker.State.Value) + { + case DownloadState.NotDownloaded: + if (!BeatmapSet.Availability.DownloadDisabled) beatmaps?.Download(BeatmapSet, preferNoVideo.Value); + break; + + case DownloadState.LocallyAvailable: + game?.PresentBeatmap(BeatmapSet); + break; } - else - beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); - }; + } + else + beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 9428984115..75fdc7d7e8 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -46,6 +46,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards : base(beatmapSet, allowExpansion) { content = new BeatmapCardContent(height); + + Action = DefaultAction; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs index 62108fe6f5..c23a03aabe 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs @@ -54,6 +54,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards : base(beatmapSet, false) { content = new BeatmapCardContent(height); + + Action = DefaultAction; } [BackgroundDependencyLoader] diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs index 505a6fcdae..ac9ee94f56 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs @@ -47,6 +47,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards : base(beatmapSet, allowExpansion) { content = new BeatmapCardContent(HEIGHT); + + Action = DefaultAction; } [BackgroundDependencyLoader] From 41698d58483a03a82136f730cef438b4d46c8201 Mon Sep 17 00:00:00 2001 From: AeroKoder Date: Mon, 29 Sep 2025 09:27:01 -0700 Subject: [PATCH 22/22] Updated `moveSelectionInBounds` in `OsuSelectionScaleHandler` to match the one in `OsuSelectionHandler` --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 3072e5d11b..d5f3137769 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void moveSelectionInBounds() { - Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!.Keys); + Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!.Keys, true); Vector2 delta = Vector2.Zero;