From 648a9d52584896db0cd3ce3cc7b5bb350bb46d32 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 23:15:09 +0900 Subject: [PATCH] Add multiplayer spectator player grid --- .../Multiplayer/Spectate/PlayerGrid.cs | 142 ++++++++++++++++++ .../Multiplayer/Spectate/PlayerGrid_Cell.cs | 78 ++++++++++ .../Multiplayer/Spectate/PlayerGrid_Facade.cs | 19 +++ 3 files changed, 239 insertions(+) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs new file mode 100644 index 0000000000..a8bd1db9dc --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -0,0 +1,142 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class PlayerGrid : CompositeDrawable + { + private const float player_spacing = 5; + + public Drawable MaximisedFacade => maximisedFacade; + + private readonly PlayerGridFacade maximisedFacade; + private readonly Container paddingContainer; + private readonly FillFlowContainer facadeContainer; + private readonly Container cellContainer; + + public PlayerGrid() + { + InternalChildren = new Drawable[] + { + paddingContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(player_spacing), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Child = facadeContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(player_spacing), + } + }, + maximisedFacade = new PlayerGridFacade { RelativeSizeAxes = Axes.Both } + } + }, + cellContainer = new Container { RelativeSizeAxes = Axes.Both } + }; + } + + public void AddContent(Drawable content) + { + var facade = new PlayerGridFacade(); + facadeContainer.Add(facade); + + var cell = new Cell(content) { ToggleMaximisationState = toggleMaximisationState }; + cell.SetFacade(facade); + + cellContainer.Add(cell); + } + + // A depth value that gets decremented every time a new instance is maximised in order to reduce underlaps. + private float maximisedInstanceDepth; + + private void toggleMaximisationState(Cell target) + { + // Iterate through all cells to ensure only one is maximised at any time. + foreach (var i in cellContainer) + { + if (i == target) + i.IsMaximised = !i.IsMaximised; + else + i.IsMaximised = false; + + if (i.IsMaximised) + { + // Transfer cell to the maximised facade. + i.SetFacade(maximisedFacade); + cellContainer.ChangeChildDepth(i, maximisedInstanceDepth -= 0.001f); + } + else + { + // Transfer cell back to its original facade. + i.SetFacade(facadeContainer[cellContainer.IndexOf(target)]); + } + } + } + + protected override void Update() + { + base.Update(); + + Vector2 cellsPerDimension; + + switch (facadeContainer.Count) + { + case 1: + cellsPerDimension = Vector2.One; + break; + + case 2: + cellsPerDimension = new Vector2(2, 1); + break; + + case 3: + case 4: + cellsPerDimension = new Vector2(2); + break; + + case 5: + case 6: + cellsPerDimension = new Vector2(3, 2); + break; + + case 7: + case 8: + case 9: + // 3 rows / 3 cols. + cellsPerDimension = new Vector2(3); + break; + + case 10: + case 11: + case 12: + // 3 rows / 4 cols. + cellsPerDimension = new Vector2(4, 3); + break; + + default: + // 4 rows / 4 cols. + cellsPerDimension = new Vector2(4); + break; + } + + // Total spacing between cells + Vector2 totalCellSpacing = player_spacing * (cellsPerDimension - Vector2.One); + + Vector2 fullSize = paddingContainer.ChildSize - totalCellSpacing; + Vector2 cellSize = Vector2.Divide(fullSize, new Vector2(cellsPerDimension.X, cellsPerDimension.Y)); + + foreach (var cell in facadeContainer) + cell.Size = cellSize; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs new file mode 100644 index 0000000000..1f6e718aa7 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs @@ -0,0 +1,78 @@ +// 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 JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class PlayerGrid + { + private class Cell : CompositeDrawable + { + public Action ToggleMaximisationState; + public bool IsMaximised; + + private PlayerGridFacade facade; + private bool isTracking = true; + + public Cell(Drawable content) + { + Origin = Anchor.Centre; + + InternalChild = content; + } + + protected override void Update() + { + base.Update(); + + if (isTracking) + { + Position = getFinalPosition(); + Size = getFinalSize(); + } + } + + public void SetFacade([NotNull] PlayerGridFacade newFacade) + { + PlayerGridFacade lastFacade = facade; + facade = newFacade; + + if (lastFacade == null || lastFacade == newFacade) + return; + + isTracking = false; + + this.MoveTo(getFinalPosition(), 400, Easing.OutQuint).ResizeTo(getFinalSize(), 400, Easing.OutQuint) + .Then() + .OnComplete(_ => + { + if (facade == newFacade) + isTracking = true; + }); + } + + private Vector2 getFinalPosition() + { + var topLeft = Parent.ToLocalSpace(facade.ToScreenSpace(Vector2.Zero)); + return topLeft + facade.DrawSize / 2; + } + + private Vector2 getFinalSize() => facade.DrawSize; + + // Todo: Temporary? + protected override bool ShouldBeConsideredForInput(Drawable child) => false; + + protected override bool OnClick(ClickEvent e) + { + ToggleMaximisationState(this); + return true; + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs new file mode 100644 index 0000000000..c565e2fec6 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs @@ -0,0 +1,19 @@ +// 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.Graphics; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class PlayerGrid + { + private class PlayerGridFacade : Drawable + { + public PlayerGridFacade() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + } + } +}