From b26a1b6330f345ac56464cf751854a60d6a986d4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Sep 2025 15:16:02 +0900 Subject: [PATCH 1/4] Add display style to PlayerPanelList --- .../Matchmaking/TestSceneMatchmakingScreen.cs | 14 + .../Visual/Matchmaking/TestScenePickScreen.cs | 2 +- .../Matchmaking/TestScenePlayerPanelList.cs | 137 ++++++++++ .../Matchmaking/Screens/Idle/IdleScreen.cs | 11 +- .../Matchmaking/Screens/Idle/PlayerPanel.cs | 5 +- .../Screens/Idle/PlayerPanelList.cs | 245 ++++++++++++++++-- .../Screens/MatchmakingScreenStack.cs | 71 ++--- .../Screens/MatchmakingSubScreen.cs | 16 +- .../Matchmaking/Screens/Pick/PickScreen.cs | 30 ++- .../Screens/Results/ResultsScreen.cs | 18 +- .../RoundResults/RoundResultsScreen.cs | 4 + 11 files changed, 473 insertions(+), 80 deletions(-) create mode 100644 osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs diff --git a/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreen.cs b/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreen.cs index 416811d345..c155cd2aed 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreen.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreen.cs @@ -53,6 +53,20 @@ namespace osu.Game.Tests.Visual.Matchmaking WaitForJoined(); + AddStep("join users", () => + { + for (int i = 0; i < 7; i++) + { + MultiplayerClient.AddUser(new MultiplayerRoomUser(i) + { + User = new APIUser + { + Username = $"User {i}" + } + }); + } + }); + setupRequestHandler(); AddStep("load match", () => diff --git a/osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs b/osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs index 16f687d772..6d9e802b65 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Matchmaking PickScreen screen = null!; - AddStep("add screen", () => LoadScreen(screen = new PickScreen())); + AddStep("add screen", () => Child = screen = new PickScreen()); AddStep("select maps", () => { diff --git a/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs b/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs new file mode 100644 index 0000000000..151bd3f02b --- /dev/null +++ b/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs @@ -0,0 +1,137 @@ +// 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.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; +using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; +using osu.Game.Tests.Visual.Multiplayer; +using osuTK; + +namespace osu.Game.Tests.Visual.Matchmaking +{ + public partial class TestScenePlayerPanelList : MultiplayerTestScene + { + private PlayerPanelList list = null!; + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("join room", () => JoinRoom(CreateDefaultRoom())); + WaitForJoined(); + + AddStep("add list", () => Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + Child = list = new PlayerPanelList() + }); + } + + [Test] + public void TestChangeDisplayMode() + { + AddStep("join users", () => + { + for (int i = 0; i < 7; i++) + { + MultiplayerClient.AddUser(new MultiplayerRoomUser(i) + { + User = new APIUser + { + Username = $"User {i}" + } + }); + } + }); + + AddStep("change to split mode", () => list.DisplayStyle = PlayerPanelList.PanelDisplayStyle.Split); + AddStep("change to grid mode", () => list.DisplayStyle = PlayerPanelList.PanelDisplayStyle.Grid); + AddStep("change to hidden mode", () => list.DisplayStyle = PlayerPanelList.PanelDisplayStyle.Hidden); + } + + [Test] + public void AddPanelsGrid() + { + AddStep("change to grid mode", () => list.DisplayStyle = PlayerPanelList.PanelDisplayStyle.Grid); + + int userId = 0; + + AddRepeatStep("join user", () => + { + MultiplayerClient.AddUser(new MultiplayerRoomUser(userId) + { + User = new APIUser + { + Username = $"User {userId}" + } + }); + + userId++; + }, 8); + } + + [Test] + public void AddPanelsSplit() + { + AddStep("change to split mode", () => list.DisplayStyle = PlayerPanelList.PanelDisplayStyle.Split); + + int userId = 0; + + AddRepeatStep("join user", () => + { + MultiplayerClient.AddUser(new MultiplayerRoomUser(userId) + { + User = new APIUser + { + Username = $"User {userId}" + } + }); + + userId++; + }, 8); + } + + [Test] + public void ChangeRankings() + { + AddStep("join users", () => + { + for (int i = 0; i < 7; i++) + { + MultiplayerClient.AddUser(new MultiplayerRoomUser(i) + { + User = new APIUser + { + Username = $"User {i}" + } + }); + } + }); + + AddStep("set random placements", () => + { + MultiplayerRoom room = MultiplayerClient.ServerRoom!; + + int[] placements = Enumerable.Range(1, room.Users.Count).ToArray(); + Random.Shared.Shuffle(placements); + + MatchmakingRoomState state = new MatchmakingRoomState(); + + for (int i = 0; i < room.Users.Count; i++) + state.Users[room.Users[i].UserID].Placement = placements[i]; + + MultiplayerClient.ChangeMatchRoomState(state).WaitSafely(); + }); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/IdleScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/IdleScreen.cs index e67e2a520a..6f982d89f2 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/IdleScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/IdleScreen.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -9,14 +8,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle { public partial class IdleScreen : MatchmakingSubScreen { - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new PlayerPanelList - { - RelativeSizeAxes = Axes.Both - }; - } + public override PlayerPanelList.PanelDisplayStyle PlayersDisplayStyle => PlayerPanelList.PanelDisplayStyle.Grid; + public override Drawable PlayersDisplayArea => this; public override void OnEntering(ScreenTransitionEvent e) { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs index eaddb0f2e4..1a0e24d5ba 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs @@ -18,6 +18,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle { 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); + public readonly MultiplayerRoomUser RoomUser; [Resolved] @@ -141,7 +144,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle double duration = instant ? 0 : 1000; avatar.MoveTo(avatarPosition, duration, Easing.OutPow10); - this.ResizeTo(horizontal ? new Vector2(250, 100) : new Vector2(150, 200), duration, Easing.OutPow10); + this.ResizeTo(horizontal ? SIZE_HORIZONTAL : SIZE_VERTICAL, duration, Easing.OutPow10); rankText.MoveTo(horizontal ? new Vector2(-40, -10) : new Vector2(-70, 0), duration, Easing.OutPow10); username.MoveTo(horizontal ? new Vector2(0, -46) : new Vector2(0, -86), duration, Easing.OutPow10); diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs index aa294f5bd3..111471273a 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs @@ -1,11 +1,13 @@ // 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 osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; using osuTK; @@ -17,19 +19,47 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle [Resolved] private MultiplayerClient client { get; set; } = null!; - public bool Horizontal { get; init; } + private Container panels = null!; + private PlayerPanelCellContainer gridLayout = null!; + private PlayerPanelCellContainer splitLayoutLeft = null!; + private PlayerPanelCellContainer splitLayoutRight = null!; - private FillFlowContainer panels = null!; + private PanelDisplayStyle displayStyle; + private Drawable? displayArea; + private bool isAnimatingToDisplayArea; [BackgroundDependencyLoader] private void load() { - InternalChild = panels = new FillFlowContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Spacing = new Vector2(20, 5), - LayoutEasing = Easing.InOutQuint, - LayoutDuration = 500 + gridLayout = new PlayerPanelCellContainer + { + RelativeSizeAxes = Axes.Both, + Spacing = new Vector2(20, 5), + }, + splitLayoutLeft = new PlayerPanelCellContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20, 5), + }, + splitLayoutRight = new PlayerPanelCellContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20, 5), + }, + panels = new Container + { + RelativeSizeAxes = Axes.Both + } }; } @@ -37,6 +67,10 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle { base.LoadComplete(); + // Set position/size so we don't initially animate. + Position = getFinalPosition(); + Size = getFinalSize(); + client.MatchRoomStateChanged += onRoomStateChanged; client.UserJoined += onUserJoined; client.UserLeft += onUserLeft; @@ -47,36 +81,117 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle foreach (var user in client.Room.Users) onUserJoined(user); } + + updateDisplay(); + } + + public PanelDisplayStyle DisplayStyle + { + set + { + displayStyle = value; + if (IsLoaded) + updateDisplay(); + } + } + + public Drawable? DisplayArea + { + set + { + displayArea = value; + isAnimatingToDisplayArea = true; + } } private void onUserJoined(MultiplayerRoomUser user) => Scheduler.Add(() => { panels.Add(new PlayerPanel(user) { - Horizontal = Horizontal, Anchor = Anchor.Centre, Origin = Anchor.Centre, }); + + updateDisplay(); }); private void onUserLeft(MultiplayerRoomUser user) => Scheduler.Add(() => { panels.Single(p => p.RoomUser.Equals(user)).Expire(); + updateDisplay(); }); - private void onRoomStateChanged(MatchRoomState? state) => Scheduler.Add(() => + private void onRoomStateChanged(MatchRoomState? state) => Scheduler.Add(updateDisplay); + + private void updateDisplay() { - if (state is not MatchmakingRoomState matchmakingState) - return; + gridLayout.ReleasePanels(); + splitLayoutLeft.ReleasePanels(); + splitLayoutRight.ReleasePanels(); - foreach (var panel in panels) + switch (displayStyle) { - if (matchmakingState.Users.UserDictionary.TryGetValue(panel.User.Id, out MatchmakingUser? user)) - panels.SetLayoutPosition(panel, user.Placement); - else - panels.SetLayoutPosition(panel, float.MaxValue); + case PanelDisplayStyle.Grid: + foreach (var panel in panels) + { + panel.FadeTo(1, 200); + panel.Horizontal = false; + } + + gridLayout.AcquirePanels(panels.ToArray()); + break; + + case PanelDisplayStyle.Split: + foreach (var panel in panels) + { + panel.FadeTo(1, 200); + panel.Horizontal = true; + } + + int leftCount = (int)Math.Ceiling(panels.Count / 2f); + + splitLayoutLeft.AcquirePanels(panels.Take(leftCount).ToArray()); + splitLayoutRight.AcquirePanels(panels.Skip(leftCount).ToArray()); + break; + + case PanelDisplayStyle.Hidden: + foreach (var panel in panels) + panel.FadeTo(0, 200); + return; } - }); + } + + protected override void Update() + { + base.Update(); + + var targetPos = getFinalPosition(); + var targetSize = getFinalSize(); + + double duration = isAnimatingToDisplayArea ? 60 : 0; + + if (Time.Elapsed > 0) + { + Position = new Vector2( + (float)Interpolation.DampContinuously(Position.X, targetPos.X, duration, Time.Elapsed), + (float)Interpolation.DampContinuously(Position.Y, targetPos.Y, duration, Time.Elapsed) + ); + + Size = new Vector2( + (float)Interpolation.DampContinuously(Size.X, targetSize.X, duration, Time.Elapsed), + (float)Interpolation.DampContinuously(Size.Y, targetSize.Y, duration, Time.Elapsed) + ); + } + + // If we don't track the animating state, the animation will also occur when resizing the window. + isAnimatingToDisplayArea &= !Precision.AlmostEquals(Size, targetSize, 0.5f); + } + + private Vector2 getFinalPosition() + => displayArea == null ? Vector2.Zero : Parent!.ToLocalSpace(displayArea.ScreenSpaceDrawQuad.TopLeft); + + private Vector2 getFinalSize() + => displayArea == null ? Parent!.DrawSize : Parent!.ToLocalSpace(displayArea.ScreenSpaceDrawQuad.BottomRight) - Parent!.ToLocalSpace(displayArea.ScreenSpaceDrawQuad.TopLeft); protected override void Dispose(bool isDisposing) { @@ -89,5 +204,101 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle client.UserLeft -= onUserLeft; } } + + private partial class PlayerPanelCellContainer : FillFlowContainer + { + [Resolved] + private MultiplayerClient client { get; set; } = null!; + + public void AcquirePanels(PlayerPanel[] panels) + { + while (Count < panels.Length) + { + Add(new PlayerPanelCell + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + } + + while (Count > panels.Length) + Remove(Children[^1], true); + + for (int i = 0; i < panels.Length; i++) + { + // We'll invalidate the layout position to represent the new placements and the re-flow will happen in UpdateAfterChildren(). + // But the cells expect their positions to be valid as they're updated, which won't be the case until the re-flow happens. + int i2 = i; + ScheduleAfterChildren(() => Children[i2].AcquirePanel(panels[i2])); + + if (client.Room?.MatchState is not MatchmakingRoomState matchmakingState) + continue; + + if (matchmakingState.Users.UserDictionary.TryGetValue(panels[i].User.Id, out MatchmakingUser? user)) + SetLayoutPosition(Children[i], user.Placement); + else + SetLayoutPosition(Children[i], float.MaxValue); + } + } + + public void ReleasePanels() + { + foreach (var panel in Children) + panel.ReleasePanel(); + } + } + + private partial class PlayerPanelCell : Drawable + { + private PlayerPanel? panel; + private bool isAnimating; + + public void AcquirePanel(PlayerPanel panel) + { + this.panel = panel; + isAnimating = true; + } + + public void ReleasePanel() + { + panel = null; + } + + protected override void Update() + { + base.Update(); + + if (panel == null) + return; + + Size = panel.Horizontal ? PlayerPanel.SIZE_HORIZONTAL : PlayerPanel.SIZE_VERTICAL; + Size *= panel.Scale; + + var targetPos = getFinalPosition(); + + double duration = isAnimating ? 60 : 0; + + if (Time.Elapsed > 0) + { + panel.Position = new Vector2( + (float)Interpolation.DampContinuously(panel.Position.X, targetPos.X, duration, Time.Elapsed), + (float)Interpolation.DampContinuously(panel.Position.Y, targetPos.Y, duration, Time.Elapsed) + ); + } + + // If we don't track the animating state, the animation will also occur when resizing the window. + isAnimating &= !Precision.AlmostEquals(panel.Position, targetPos, 0.5f); + + Vector2 getFinalPosition() + => panel.Parent!.ToLocalSpace(ScreenSpaceDrawQuad.Centre) - panel.AnchorPosition; + } + } + + public enum PanelDisplayStyle + { + Grid, + Split, + Hidden + } } } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingScreenStack.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingScreenStack.cs index cba5c89385..0b34beacc7 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingScreenStack.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingScreenStack.cs @@ -13,7 +13,6 @@ using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Pick; using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Results; using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.RoundResults; -using osuTK; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens { @@ -23,6 +22,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens private MultiplayerClient client { get; set; } = null!; private ScreenStack screenStack = null!; + private PlayerPanelList playersList = null!; [BackgroundDependencyLoader] private void load() @@ -30,40 +30,28 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens RelativeSizeAxes = Axes.Both; Padding = new MarginPadding(10); - InternalChild = new GridContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] { new Dimension(), new Dimension(GridSizeMode.AutoSize) }, - Content = new Drawable[][] + new GridContainer { - [ - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] { new Dimension(), new Dimension(GridSizeMode.Absolute, 20), new Dimension(GridSizeMode.AutoSize) }, - Padding = new MarginPadding { Bottom = 20 }, - Content = new Drawable?[][] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { new Dimension(), new Dimension(GridSizeMode.AutoSize) }, + Content = new Drawable[][] + { + [ + screenStack = new ScreenStack(), + ], + [ + new StageDisplay { - [ - screenStack = new ScreenStack(), - null, - new PlayerPanelList - { - Horizontal = true, - RelativeSizeAxes = Axes.Y, - Width = 250, - Scale = new Vector2(0.8f), - } - ] + RelativeSizeAxes = Axes.X } - } - ], - [ - new StageDisplay - { - RelativeSizeAxes = Axes.X - } - ] + ] + } + }, + playersList = new PlayerPanelList + { + DisplayArea = this } }; } @@ -72,12 +60,33 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens { base.LoadComplete(); + screenStack.ScreenPushed += onScreenPushed; + screenStack.ScreenExited += onScreenExited; + screenStack.Push(new IdleScreen()); client.MatchRoomStateChanged += onMatchRoomStateChanged; onMatchRoomStateChanged(client.Room!.MatchState); } + private void onScreenPushed(IScreen lastScreen, IScreen newScreen) + { + if (newScreen is not MatchmakingSubScreen matchmakingSubScreen) + return; + + playersList.DisplayStyle = matchmakingSubScreen.PlayersDisplayStyle; + playersList.DisplayArea = matchmakingSubScreen.PlayersDisplayArea; + } + + private void onScreenExited(IScreen lastScreen, IScreen newScreen) + { + if (newScreen is not MatchmakingSubScreen matchmakingSubScreen) + return; + + playersList.DisplayStyle = matchmakingSubScreen.PlayersDisplayStyle; + playersList.DisplayArea = matchmakingSubScreen.PlayersDisplayArea; + } + private void onMatchRoomStateChanged(MatchRoomState? state) => Scheduler.Add(() => { if (state is not MatchmakingRoomState matchmakingState) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs index 86a46546ca..d14739c021 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs @@ -3,12 +3,16 @@ using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens { - public partial class MatchmakingSubScreen : Screen + public abstract partial class MatchmakingSubScreen : Screen { - public MatchmakingSubScreen() + public abstract PlayerPanelList.PanelDisplayStyle PlayersDisplayStyle { get; } + public abstract Drawable? PlayersDisplayArea { get; } + + protected MatchmakingSubScreen() { RelativePositionAxes = Axes.X; } @@ -16,19 +20,19 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); - this.MoveToX(1).MoveToX(0, 200); + this.FadeInFromZero(200); } public override void OnSuspending(ScreenTransitionEvent e) { base.OnSuspending(e); - this.MoveToX(-1, 200); + this.FadeOutFromOne(200); } public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); - this.MoveToX(0, 200); + this.FadeInFromZero(200); } public override bool OnExiting(ScreenExitEvent e) @@ -36,7 +40,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens if (base.OnExiting(e)) return true; - this.MoveToX(1, 200); + this.FadeOutFromOne(200); return false; } } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs index 73e2188273..2a49030adf 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs @@ -8,26 +8,42 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Pick { - public partial class PickScreen : OsuScreen + public partial class PickScreen : MatchmakingSubScreen { - private BeatmapSelectionGrid selectionGrid = null!; + public override PlayerPanelList.PanelDisplayStyle PlayersDisplayStyle => PlayerPanelList.PanelDisplayStyle.Split; + public override Drawable PlayersDisplayArea { get; } + + private readonly BeatmapSelectionGrid selectionGrid; [Resolved] private MultiplayerClient client { get; set; } = null!; - [BackgroundDependencyLoader] - private void load() + public PickScreen() { - InternalChild = new Container + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Child = selectionGrid = new BeatmapSelectionGrid + new Container { RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 200 }, + Child = selectionGrid = new BeatmapSelectionGrid + { + RelativeSizeAxes = Axes.Both, + }, }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 5 }, + Child = PlayersDisplayArea = Empty().With(d => + { + d.RelativeSizeAxes = Axes.Both; + }) + } }; } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs index 3fe4cc6d7a..83a7f0b7b6 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs @@ -21,15 +21,17 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Results { private const float grid_spacing = 5; + public override PlayerPanelList.PanelDisplayStyle PlayersDisplayStyle => PlayerPanelList.PanelDisplayStyle.Grid; + public override Drawable PlayersDisplayArea { get; } + [Resolved] private MultiplayerClient client { get; set; } = null!; - private OsuSpriteText placementText = null!; - private FillFlowContainer userStatistics = null!; - private FillFlowContainer roomStatistics = null!; + private readonly OsuSpriteText placementText; + private readonly FillFlowContainer userStatistics; + private readonly FillFlowContainer roomStatistics; - [BackgroundDependencyLoader] - private void load() + public ResultsScreen() { InternalChild = new GridContainer { @@ -113,10 +115,10 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Results } }, null, - new PlayerPanelList + PlayersDisplayArea = Empty().With(d => { - RelativeSizeAxes = Axes.Both - } + d.RelativeSizeAxes = Axes.Both; + }) ] } } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/RoundResults/RoundResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/RoundResults/RoundResultsScreen.cs index d7837e96c6..71d19c1791 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/RoundResults/RoundResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/RoundResults/RoundResultsScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; using osu.Game.Screens.Ranking; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.RoundResults @@ -29,6 +30,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.RoundResults { private const int panel_spacing = 5; + public override PlayerPanelList.PanelDisplayStyle PlayersDisplayStyle => PlayerPanelList.PanelDisplayStyle.Hidden; + public override Drawable? PlayersDisplayArea => null; + [Resolved] private IAPIProvider api { get; set; } = null!; From 5eaf376a607ee677e083e73928d5d699a061c8ef Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Sep 2025 15:16:48 +0900 Subject: [PATCH 2/4] Decrease scale of panels --- .../Matchmaking/Screens/Idle/PlayerPanel.cs | 69 ++++++++++--------- .../Screens/Idle/PlayerPanelList.cs | 1 + 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs index 1a0e24d5ba..d24e17b9b1 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs @@ -35,6 +35,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle private MatchmakingAvatar avatar = null!; private OsuSpriteText username = null!; + private Container scaleContainer = null!; private Container mainContent = null!; public bool Horizontal @@ -62,41 +63,47 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle Masking = true; CornerRadius = 10; - Add(mainContent = new Container + Add(scaleContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = mainContent = new Container { - avatar = new MatchmakingAvatar(User, isOwnUser: User.Id == api.LocalUser.Value.Id) + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.TopLeft, - Origin = Anchor.Centre, - Size = new Vector2(80), - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomCentre, - Blending = BlendingParameters.Additive, - Margin = new MarginPadding(4), - Font = OsuFont.Style.Title.With(size: 70), - }, - username = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Text = User.Username, - Font = OsuFont.Style.Heading1, - }, - scoreText = new OsuSpriteText - { - Margin = new MarginPadding(10), - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = OsuFont.Style.Heading2, - Text = "0 pts" + avatar = new MatchmakingAvatar(User, isOwnUser: User.Id == api.LocalUser.Value.Id) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.Centre, + Size = new Vector2(80), + }, + rankText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomCentre, + Blending = BlendingParameters.Additive, + Margin = new MarginPadding(4), + Font = OsuFont.Style.Title.With(size: 70), + }, + username = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Text = User.Username, + Font = OsuFont.Style.Heading1, + }, + scoreText = new OsuSpriteText + { + Margin = new MarginPadding(10), + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.Style.Heading2, + Text = "0 pts" + } } } }); @@ -153,14 +160,14 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle protected override bool OnHover(HoverEvent e) { - this.ScaleTo(1.02f, 1000, Easing.OutQuint); + scaleContainer.ScaleTo(1.02f, 1000, Easing.OutQuint); mainContent.ScaleTo(1.03f, 1000, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - this.ScaleTo(1f, 500, Easing.OutQuint); + scaleContainer.ScaleTo(1f, 500, Easing.OutQuint); mainContent.ScaleTo(1, 500, Easing.OutQuint); mainContent.MoveTo(Vector2.Zero, 500, Easing.OutElasticHalf); diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs index 111471273a..3683198821 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs @@ -110,6 +110,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Scale = new Vector2(0.8f) }); updateDisplay(); From c08d88eb7f2c862d104acfaa01126cf9db55b6c3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Sep 2025 16:11:40 +0900 Subject: [PATCH 3/4] Adjust namespaces --- osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs | 2 +- osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs | 2 +- .../OnlinePlay/Matchmaking/{Screens/Idle => }/PlayerPanel.cs | 2 +- .../Matchmaking/{Screens/Idle => }/PlayerPanelList.cs | 2 +- .../OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs | 1 - .../Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs | 1 - .../OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs | 1 - .../Matchmaking/Screens/RoundResults/RoundResultsScreen.cs | 1 - 8 files changed, 4 insertions(+), 8 deletions(-) rename osu.Game/Screens/OnlinePlay/Matchmaking/{Screens/Idle => }/PlayerPanel.cs (99%) rename osu.Game/Screens/OnlinePlay/Matchmaking/{Screens/Idle => }/PlayerPanelList.cs (99%) diff --git a/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs b/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs index dafb2d9f03..f98a6aac99 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanel.cs @@ -7,7 +7,7 @@ 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.Screens.OnlinePlay.Matchmaking.Screens.Idle; +using osu.Game.Screens.OnlinePlay.Matchmaking; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Users; diff --git a/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs b/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs index 151bd3f02b..17423c9852 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestScenePlayerPanelList.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; -using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; +using osu.Game.Screens.OnlinePlay.Matchmaking; using osu.Game.Tests.Visual.Multiplayer; using osuTK; diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/PlayerPanel.cs similarity index 99% rename from osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs rename to osu.Game/Screens/OnlinePlay/Matchmaking/PlayerPanel.cs index d24e17b9b1..42b1edde9b 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/PlayerPanel.cs @@ -14,7 +14,7 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; using osu.Game.Users; using osuTK; -namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle +namespace osu.Game.Screens.OnlinePlay.Matchmaking { public partial class PlayerPanel : UserPanel { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/PlayerPanelList.cs similarity index 99% rename from osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs rename to osu.Game/Screens/OnlinePlay/Matchmaking/PlayerPanelList.cs index 3683198821..fa2c515f77 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Idle/PlayerPanelList.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/PlayerPanelList.cs @@ -12,7 +12,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; using osuTK; -namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle +namespace osu.Game.Screens.OnlinePlay.Matchmaking { public partial class PlayerPanelList : CompositeDrawable { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs index d14739c021..fc41b7db84 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/MatchmakingSubScreen.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Screens; -using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs index 2a49030adf..96cfa67642 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Pick/PickScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Pick { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs index 83a7f0b7b6..83c587e7cd 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/Results/ResultsScreen.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; using osu.Game.Utils; using osuTK; diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/RoundResults/RoundResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/RoundResults/RoundResultsScreen.cs index 71d19c1791..8fd56877eb 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/RoundResults/RoundResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Screens/RoundResults/RoundResultsScreen.cs @@ -21,7 +21,6 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Scoring; -using osu.Game.Screens.OnlinePlay.Matchmaking.Screens.Idle; using osu.Game.Screens.Ranking; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Screens.RoundResults From 87061959ea3258b2487a6c4c7699638bac71f496 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Sep 2025 12:49:34 +0900 Subject: [PATCH 4/4] Fix failing screen test --- osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs b/osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs index 6d9e802b65..b12fff385b 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestScenePickScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; @@ -80,7 +81,7 @@ namespace osu.Game.Tests.Visual.Matchmaking PickScreen screen = null!; - AddStep("add screen", () => Child = screen = new PickScreen()); + AddStep("add screen", () => Child = new ScreenStack(screen = new PickScreen())); AddStep("select maps", () => {