1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-18 19:50:31 +08:00
Files
osu-lazer/osu.Game/Screens/OnlinePlay/Matchmaking/PlayerPanelList.cs
T
2025-09-24 14:44:27 +09:00

310 lines
10 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
namespace osu.Game.Screens.OnlinePlay.Matchmaking
{
public partial class PlayerPanelList : CompositeDrawable
{
[Resolved]
private MultiplayerClient client { get; set; } = null!;
private Container<PlayerPanel> panels = null!;
private PlayerPanelCellContainer gridLayout = null!;
private PlayerPanelCellContainer splitLayoutLeft = null!;
private PlayerPanelCellContainer splitLayoutRight = null!;
private PanelDisplayStyle displayStyle;
private Drawable? displayArea;
private bool isAnimatingToDisplayArea;
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
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<PlayerPanel>
{
RelativeSizeAxes = Axes.Both
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
// Set position/size so we don't initially animate.
Position = getFinalPosition();
Size = getFinalSize();
client.MatchRoomStateChanged += onRoomStateChanged;
client.UserJoined += onUserJoined;
client.UserLeft += onUserLeft;
if (client.Room != null)
{
onRoomStateChanged(client.Room.MatchState);
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)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(0.8f)
});
updateDisplay();
});
private void onUserLeft(MultiplayerRoomUser user) => Scheduler.Add(() =>
{
panels.Single(p => p.RoomUser.Equals(user)).Expire();
updateDisplay();
});
private void onRoomStateChanged(MatchRoomState? state) => Scheduler.Add(updateDisplay);
private void updateDisplay()
{
gridLayout.ReleasePanels();
splitLayoutLeft.ReleasePanels();
splitLayoutRight.ReleasePanels();
switch (displayStyle)
{
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)
{
base.Dispose(isDisposing);
if (client.IsNotNull())
{
client.MatchRoomStateChanged -= onRoomStateChanged;
client.UserJoined -= onUserJoined;
client.UserLeft -= onUserLeft;
}
}
private partial class PlayerPanelCellContainer : FillFlowContainer<PlayerPanelCell>
{
[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()
{
// Matches the schedule in AcquirePanels.
ScheduleAfterChildren(() =>
{
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
}
}
}