diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 11649da2f1..4f9048b988 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy { var keyCounter = container.OfType().FirstOrDefault(); var spectatorList = container.OfType().FirstOrDefault(); + var leaderboard = container.OfType().FirstOrDefault(); if (keyCounter != null) { @@ -64,12 +65,20 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy spectatorList.Origin = Anchor.BottomLeft; spectatorList.Position = new Vector2(10, -10); } + + if (leaderboard != null) + { + leaderboard.Anchor = Anchor.CentreLeft; + leaderboard.Origin = Anchor.CentreLeft; + leaderboard.X = 10; + } }) { Children = new Drawable[] { new LegacyKeyCounterDisplay(), new SpectatorList(), + new DrawableGameplayLeaderboard(), } }; } diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 6f010ffe48..7c7eb01051 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -40,9 +40,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => { + var leaderboard = container.OfType().FirstOrDefault(); var combo = container.ChildrenOfType().FirstOrDefault(); var spectatorList = container.OfType().FirstOrDefault(); + if (leaderboard != null) + leaderboard.Position = new Vector2(36, 115); + if (combo != null) { combo.ShowLabel.Value = false; @@ -55,6 +59,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon spectatorList.Position = new Vector2(36, -66); }) { + new DrawableGameplayLeaderboard(), new ArgonManiaComboCounter(), new SpectatorList { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index c321fcda87..f0d8430f71 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { var combo = container.ChildrenOfType().FirstOrDefault(); var spectatorList = container.OfType().FirstOrDefault(); + var leaderboard = container.OfType().FirstOrDefault(); if (combo != null) { @@ -112,10 +113,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy spectatorList.Origin = Anchor.BottomLeft; spectatorList.Position = new Vector2(10, -10); } + + if (leaderboard != null) + { + leaderboard.Anchor = Anchor.CentreLeft; + leaderboard.Origin = Anchor.CentreLeft; + leaderboard.X = 10; + } }) { new LegacyManiaComboCounter(), new SpectatorList(), + new DrawableGameplayLeaderboard(), }; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d39e05b262..af1df6dc9c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var combo = container.OfType().FirstOrDefault(); var spectatorList = container.OfType().FirstOrDefault(); + var leaderboard = container.OfType().FirstOrDefault(); Vector2 pos = new Vector2(); @@ -89,6 +90,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy spectatorList.Anchor = Anchor.BottomLeft; spectatorList.Origin = Anchor.BottomLeft; spectatorList.Position = pos; + + // maximum height of the spectator list is around ~172 units + pos += new Vector2(0, -185); + } + + if (leaderboard != null) + { + leaderboard.Anchor = Anchor.BottomLeft; + leaderboard.Origin = Anchor.BottomLeft; + leaderboard.Position = pos; } }) { @@ -97,6 +108,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy new LegacyDefaultComboCounter(), new LegacyKeyCounterDisplay(), new SpectatorList(), + new DrawableGameplayLeaderboard(), } }; } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs index 26bb1900b9..b0a1c5d3f7 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Skinning.Argon { @@ -18,6 +21,59 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon { switch (lookup) { + case GlobalSkinnableContainerLookup containerLookup: + // Only handle per ruleset defaults here. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); + + switch (containerLookup.Lookup) + { + case GlobalSkinnableContainers.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var leaderboard = container.OfType().FirstOrDefault(); + var comboCounter = container.OfType().FirstOrDefault(); + var spectatorList = container.OfType().FirstOrDefault(); + + if (leaderboard != null) + { + leaderboard.Anchor = leaderboard.Origin = Anchor.BottomLeft; + leaderboard.Position = new Vector2(36, -140); + leaderboard.Height = 140; + } + + if (comboCounter != null) + comboCounter.Position = new Vector2(36, -66); + + if (spectatorList != null) + { + spectatorList.Position = new Vector2(320, -280); + spectatorList.Anchor = Anchor.BottomLeft; + spectatorList.Origin = Anchor.TopLeft; + } + }) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new DrawableGameplayLeaderboard(), + new ArgonComboCounter + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Scale = new Vector2(1.3f), + }, + new SpectatorList + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + }, + }; + } + + return null; + case SkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/TaikoTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/TaikoTrianglesSkinTransformer.cs new file mode 100644 index 0000000000..f627417889 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/TaikoTrianglesSkinTransformer.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Skinning.Default +{ + public class TaikoTrianglesSkinTransformer : SkinTransformer + { + public TaikoTrianglesSkinTransformer(ISkin skin) + : base(skin) + { + } + + public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) + { + switch (lookup) + { + case GlobalSkinnableContainerLookup containerLookup: + { + // Only handle per ruleset defaults here. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); + + switch (containerLookup.Lookup) + { + case GlobalSkinnableContainers.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var leaderboard = container.OfType().FirstOrDefault(); + var spectatorList = container.OfType().FirstOrDefault(); + + if (leaderboard != null) + { + leaderboard.Position = new Vector2(40, -100); + leaderboard.Height = 180; + leaderboard.Anchor = Anchor.BottomLeft; + leaderboard.Origin = Anchor.BottomLeft; + } + + if (spectatorList != null) + { + spectatorList.HeaderFont.Value = Typeface.Venera; + spectatorList.HeaderColour.Value = new OsuColour().BlueLighter; + spectatorList.Anchor = Anchor.BottomLeft; + spectatorList.Origin = Anchor.TopLeft; + spectatorList.Position = new Vector2(320, -280); + } + }) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new DrawableGameplayLeaderboard(), + new SpectatorList + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + }, + }; + } + + return null; + } + } + + return base.GetDrawableComponent(lookup); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index c6221e0589..8f1f1da7ee 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -3,12 +3,15 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Game.Audio; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { @@ -29,119 +32,180 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is SkinComponentLookup) + switch (lookup) { - // if a taiko skin is providing explosion sprites, hide the judgements completely - if (hasExplosion.Value) - return Drawable.Empty().With(d => d.Expire()); - } - - if (lookup is TaikoSkinComponentLookup taikoComponent) - { - switch (taikoComponent.Component) + case GlobalSkinnableContainerLookup containerLookup: { - case TaikoSkinComponents.DrumRollBody: - if (GetTexture("taiko-roll-middle") != null) - return new LegacyDrumRoll(); + // Modifications for global components. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); + // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). + if (!IsProvidingLegacyResources) return null; - case TaikoSkinComponents.InputDrum: - if (hasBarLeft) - return new LegacyInputDrum(); + switch (containerLookup.Lookup) + { + case GlobalSkinnableContainers.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var combo = container.OfType().FirstOrDefault(); + var spectatorList = container.OfType().FirstOrDefault(); + var leaderboard = container.OfType().FirstOrDefault(); - return null; + Vector2 pos = new Vector2(); - case TaikoSkinComponents.DrumSamplePlayer: - return null; + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); - case TaikoSkinComponents.CentreHit: - case TaikoSkinComponents.RimHit: - if (hasHitCircle) - return new LegacyHit(taikoComponent.Component); + pos += new Vector2(10, -(combo.DrawHeight * 1.56f + 20) * combo.Scale.X); + } - return null; + if (leaderboard != null) + { + leaderboard.Anchor = Anchor.BottomLeft; + leaderboard.Origin = Anchor.BottomLeft; + leaderboard.Position = pos; + leaderboard.Height = 170; + pos += new Vector2(10 + leaderboard.Width, -leaderboard.Height); + } - case TaikoSkinComponents.DrumRollTick: - return this.GetAnimation("sliderscorepoint", false, false); + if (spectatorList != null) + { + spectatorList.Anchor = Anchor.BottomLeft; + spectatorList.Origin = Anchor.TopLeft; + spectatorList.Position = pos; + } + }) + { + new LegacyDefaultComboCounter(), + new SpectatorList(), + new DrawableGameplayLeaderboard(), + }; + } - case TaikoSkinComponents.Swell: - if (GetTexture("spinner-circle") != null) - return new LegacySwell(); + return null; + } - return null; + case SkinComponentLookup: + { + // if a taiko skin is providing explosion sprites, hide the judgements completely + if (hasExplosion.Value) + return Drawable.Empty().With(d => d.Expire()); - case TaikoSkinComponents.HitTarget: - if (GetTexture("taikobigcircle") != null) - return new TaikoLegacyHitTarget(); + break; + } - return null; + case TaikoSkinComponentLookup taikoComponent: + { + switch (taikoComponent.Component) + { + case TaikoSkinComponents.DrumRollBody: + if (GetTexture("taiko-roll-middle") != null) + return new LegacyDrumRoll(); - case TaikoSkinComponents.PlayfieldBackgroundRight: - if (GetTexture("taiko-bar-right") != null) - return new TaikoLegacyPlayfieldBackgroundRight(); + return null; - return null; + case TaikoSkinComponents.InputDrum: + if (hasBarLeft) + return new LegacyInputDrum(); - case TaikoSkinComponents.PlayfieldBackgroundLeft: - // This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins). - if (GetTexture("taiko-bar-right") != null) - return Drawable.Empty(); + return null; - return null; + case TaikoSkinComponents.DrumSamplePlayer: + return null; - case TaikoSkinComponents.BarLine: - if (GetTexture("taiko-barline") != null) - return new LegacyBarLine(); + case TaikoSkinComponents.CentreHit: + case TaikoSkinComponents.RimHit: + if (hasHitCircle) + return new LegacyHit(taikoComponent.Component); - return null; + return null; - case TaikoSkinComponents.TaikoExplosionMiss: - var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); - if (missSprite != null) - return new LegacyHitExplosion(missSprite); + case TaikoSkinComponents.DrumRollTick: + return this.GetAnimation("sliderscorepoint", false, false); - return null; + case TaikoSkinComponents.Swell: + if (GetTexture("spinner-circle") != null) + return new LegacySwell(); - case TaikoSkinComponents.TaikoExplosionOk: - case TaikoSkinComponents.TaikoExplosionGreat: - string hitName = getHitName(taikoComponent.Component); - var hitSprite = this.GetAnimation(hitName, true, false); + return null; - if (hitSprite != null) - { - var strongHitSprite = this.GetAnimation($"{hitName}k", true, false); + case TaikoSkinComponents.HitTarget: + if (GetTexture("taikobigcircle") != null) + return new TaikoLegacyHitTarget(); - return new LegacyHitExplosion(hitSprite, strongHitSprite); - } + return null; - return null; + case TaikoSkinComponents.PlayfieldBackgroundRight: + if (GetTexture("taiko-bar-right") != null) + return new TaikoLegacyPlayfieldBackgroundRight(); - case TaikoSkinComponents.TaikoExplosionKiai: - // suppress the default kiai explosion if the skin brings its own sprites. - // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. - if (hasExplosion.Value) - return Drawable.Empty().With(d => d.Expire()); + return null; - return null; + case TaikoSkinComponents.PlayfieldBackgroundLeft: + // This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins). + if (GetTexture("taiko-bar-right") != null) + return Drawable.Empty(); - case TaikoSkinComponents.Scroller: - if (GetTexture("taiko-slider") != null) - return new LegacyTaikoScroller(); + return null; - return null; + case TaikoSkinComponents.BarLine: + if (GetTexture("taiko-barline") != null) + return new LegacyBarLine(); - case TaikoSkinComponents.Mascot: - return new DrawableTaikoMascot(); + return null; - case TaikoSkinComponents.KiaiGlow: - if (GetTexture("taiko-glow") != null) - return new LegacyKiaiGlow(); + case TaikoSkinComponents.TaikoExplosionMiss: + var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); + if (missSprite != null) + return new LegacyHitExplosion(missSprite); - return null; + return null; - default: - throw new UnsupportedSkinComponentException(lookup); + case TaikoSkinComponents.TaikoExplosionOk: + case TaikoSkinComponents.TaikoExplosionGreat: + string hitName = getHitName(taikoComponent.Component); + var hitSprite = this.GetAnimation(hitName, true, false); + + if (hitSprite != null) + { + var strongHitSprite = this.GetAnimation($"{hitName}k", true, false); + + return new LegacyHitExplosion(hitSprite, strongHitSprite); + } + + return null; + + case TaikoSkinComponents.TaikoExplosionKiai: + // suppress the default kiai explosion if the skin brings its own sprites. + // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. + if (hasExplosion.Value) + return Drawable.Empty().With(d => d.Expire()); + + return null; + + case TaikoSkinComponents.Scroller: + if (GetTexture("taiko-slider") != null) + return new LegacyTaikoScroller(); + + return null; + + case TaikoSkinComponents.Mascot: + return new DrawableTaikoMascot(); + + case TaikoSkinComponents.KiaiGlow: + if (GetTexture("taiko-glow") != null) + return new LegacyKiaiGlow(); + + return null; + + default: + throw new UnsupportedSkinComponentException(lookup); + } } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 70e429a344..24fcc570bd 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -36,6 +36,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Taiko.Configuration; using osu.Game.Rulesets.Taiko.Edit.Setup; +using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Screens.Edit.Setup; namespace osu.Game.Rulesets.Taiko @@ -57,6 +58,9 @@ namespace osu.Game.Rulesets.Taiko case ArgonSkin: return new TaikoArgonSkinTransformer(skin); + case TrianglesSkin: + return new TaikoTrianglesSkinTransformer(skin); + case LegacySkin: return new TaikoLegacySkinTransformer(skin); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlayLayouts.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlayLayouts.cs index 3b9fcd1102..ef4bd99ed3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlayLayouts.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlayLayouts.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; @@ -53,6 +54,12 @@ namespace osu.Game.Tests.Visual.Gameplay skins["legacy"] = new DefaultLegacySkin(this); } + [SetUpSteps] + public void SetUpSteps() + { + AddToggleStep("toggle leaderboard", b => configManager.SetValue(OsuSetting.GameplayLeaderboard, b)); + } + [Test] public void TestLayout( [Values("argon", "triangles", "legacy")] diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index d6f5529d4a..01e1e9f512 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -25,8 +25,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { protected override bool PauseOnFocusLost => false; - protected override bool ShowLeaderboard => true; - protected override UserActivity InitialActivity => new UserActivity.InMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); [Resolved] @@ -42,6 +40,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private readonly MultiplayerLeaderboardProvider leaderboardProvider; private GameplayMatchScoreDisplay teamScoreDisplay = null!; + private GameplayChatDisplay chat; /// /// Construct a multiplayer player. @@ -57,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AllowFailAnimation = false, AllowSkipping = room.AutoSkip, AutomaticallySkipIntro = room.AutoSkip, - AlwaysShowLeaderboard = true, + ShowLeaderboard = true, }) { leaderboardProvider = new MultiplayerLeaderboardProvider(users); @@ -71,10 +70,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer ScoreProcessor.ApplyNewJudgementsWhenFailed = true; - LoadComponentAsync(new GameplayChatDisplay(Room) - { - Expanded = { BindTarget = LeaderboardExpandedState }, - }, chat => HUDOverlay.LeaderboardFlow.Insert(2, chat)); + LoadComponentAsync(chat = new GameplayChatDisplay(Room), HUDOverlay.LeaderboardFlow.Add); LoadComponentAsync(teamScoreDisplay = new GameplayMatchScoreDisplay { @@ -124,6 +120,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer failAndBail(); } }), true); + + LocalUserPlaying.BindValueChanged(_ => chat.Expanded.Value = !LocalUserPlaying.Value, true); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUD/DrawableGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/DrawableGameplayLeaderboard.cs index 005cd784c4..b0f5e86741 100644 --- a/osu.Game/Screens/Play/HUD/DrawableGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/DrawableGameplayLeaderboard.cs @@ -13,12 +13,13 @@ using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { - public partial class DrawableGameplayLeaderboard : CompositeDrawable + public partial class DrawableGameplayLeaderboard : CompositeDrawable, ISerialisableDrawable { private readonly Cached sorting = new Cached(); @@ -31,11 +32,16 @@ namespace osu.Game.Screens.Play.HUD public DrawableGameplayLeaderboardScore? TrackedScore { get; private set; } + [Resolved] + private Player? player { get; set; } + [Resolved] private IGameplayLeaderboardProvider? leaderboardProvider { get; set; } private readonly IBindableList scores = new BindableList(); private readonly Bindable configVisibility = new Bindable(); + private readonly IBindable userPlayingState = new Bindable(); + private readonly IBindable holdingForHUD = new Bindable(); private const int max_panels = 8; @@ -45,6 +51,7 @@ namespace osu.Game.Screens.Play.HUD public DrawableGameplayLeaderboard() { Width = DrawableGameplayLeaderboardScore.EXTENDED_WIDTH + DrawableGameplayLeaderboardScore.SHEAR_WIDTH; + Height = 300; InternalChildren = new Drawable[] { @@ -67,9 +74,15 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, GameplayState? gameplayState, HUDOverlay? hudOverlay) { config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); + + if (gameplayState != null) + userPlayingState.BindTo(gameplayState.PlayingState); + + if (hudOverlay != null) + holdingForHUD.BindTo(hudOverlay.HoldingForHUD); } protected override void LoadComplete() @@ -88,7 +101,20 @@ namespace osu.Game.Screens.Play.HUD } Scheduler.AddDelayed(sort, 1000, true); - configVisibility.BindValueChanged(_ => this.FadeTo(configVisibility.Value ? 1 : 0, 100, Easing.OutQuint), true); + configVisibility.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + userPlayingState.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + holdingForHUD.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + updateState(); + } + + private void updateState() + { + // prevents weird delay in the flow correctly appearing when toggling the leaderboard on. + if (Flow.Alpha < 1) + scroll.ScrollToStart(false); + + Flow.FadeTo(player?.Configuration.ShowLeaderboard != false && configVisibility.Value ? 1 : 0, 100, Easing.OutQuint); + Expanded.Value = userPlayingState.Value == LocalUserPlayingState.Playing || holdingForHUD.Value; } /// @@ -111,10 +137,6 @@ namespace osu.Game.Screens.Play.HUD Flow.Add(drawable); drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true); drawable.DisplayOrder.BindValueChanged(_ => sorting.Invalidate(), true); - - int displayCount = Math.Min(Flow.Count, max_panels); - Height = displayCount * (DrawableGameplayLeaderboardScore.PANEL_HEIGHT + Flow.Spacing.Y); - requiresScroll = displayCount != Flow.Count; } public void Clear() @@ -131,6 +153,8 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); + requiresScroll = Flow.DrawHeight > Height; + if (requiresScroll && TrackedScore != null) { double scrollTarget = scroll.GetChildPosInContent(TrackedScore) + TrackedScore.DrawHeight / 2 - scroll.DrawHeight / 2; @@ -207,5 +231,7 @@ namespace osu.Game.Screens.Play.HUD public override bool HandlePositionalInput => false; public override bool HandleNonPositionalInput => false; } + + public bool UsesFixedAnchor { get; set; } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 733675dfb1..dd6e443249 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Play /// internal readonly Drawable PlayfieldSkinLayer; - public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) + public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods) { Container rightSettings; @@ -191,8 +191,7 @@ namespace osu.Game.Screens.Play if (rulesetComponents != null) hideTargets.Add(rulesetComponents); - if (!alwaysShowLeaderboard) - hideTargets.Add(LeaderboardFlow); + hideTargets.Add(LeaderboardFlow); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 127142e24b..6ee3ed13a0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,7 +34,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -425,8 +424,6 @@ namespace osu.Game.Screens.Play IsBreakTime.BindTo(breakTracker.IsBreakTime); IsBreakTime.BindValueChanged(onBreakTimeChanged, true); - - loadLeaderboard(); } protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); @@ -477,7 +474,7 @@ namespace osu.Game.Screens.Play Children = new[] { DimmableStoryboard.OverlayLayerContainer.CreateProxy(), - HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods, Configuration.AlwaysShowLeaderboard) + HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods) { HoldToQuit = { @@ -933,37 +930,6 @@ namespace osu.Game.Screens.Play }); } - #region Gameplay leaderboard - - protected virtual bool ShowLeaderboard => false; - - protected readonly Bindable LeaderboardExpandedState = new BindableBool(); - - private void loadLeaderboard() - { - if (!ShowLeaderboard) - return; - - HUDOverlay.HoldingForHUD.BindValueChanged(_ => updateLeaderboardExpandedState()); - LocalUserPlaying.BindValueChanged(_ => updateLeaderboardExpandedState(), true); - - var gameplayLeaderboard = new DrawableGameplayLeaderboard(); - LoadComponentAsync(gameplayLeaderboard, leaderboard => - { - if (!LoadedBeatmapSuccessfully) - return; - - leaderboard.Expanded.BindTo(LeaderboardExpandedState); - - HUDOverlay.LeaderboardFlow.Add(leaderboard); - }); - } - - private void updateLeaderboardExpandedState() => - LeaderboardExpandedState.Value = !LocalUserPlaying.Value || HUDOverlay.HoldingForHUD.Value; - - #endregion - #region Fail Logic /// diff --git a/osu.Game/Screens/Play/PlayerConfiguration.cs b/osu.Game/Screens/Play/PlayerConfiguration.cs index 466a691118..cfe8a67684 100644 --- a/osu.Game/Screens/Play/PlayerConfiguration.cs +++ b/osu.Game/Screens/Play/PlayerConfiguration.cs @@ -42,8 +42,8 @@ namespace osu.Game.Screens.Play public bool AutomaticallySkipIntro { get; set; } /// - /// Whether the gameplay leaderboard should always be shown (usually in a contracted state). + /// Whether the gameplay leaderboard should be shown. /// - public bool AlwaysShowLeaderboard { get; set; } + public bool ShowLeaderboard { get; set; } } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index c997a67dea..882e556965 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -49,8 +49,6 @@ namespace osu.Game.Screens.Play return base.CheckModsAllowFailure(); } - protected override bool ShowLeaderboard => true; - public ReplayPlayer(Score score, PlayerConfiguration configuration = null) : this((_, _) => score, configuration) { @@ -61,6 +59,7 @@ namespace osu.Game.Screens.Play : base(configuration) { this.createScore = createScore; + Configuration.ShowLeaderboard = true; } /// diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index e4e42e2f08..03a41cde15 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -19,14 +19,13 @@ namespace osu.Game.Screens.Play { public partial class SoloPlayer : SubmittingPlayer { - protected override bool ShowLeaderboard => true; - [Cached(typeof(IGameplayLeaderboardProvider))] private readonly SoloGameplayLeaderboardProvider leaderboardProvider = new SoloGameplayLeaderboardProvider(); public SoloPlayer([CanBeNull] PlayerConfiguration configuration = null) : base(configuration) { + Configuration.ShowLeaderboard = true; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index bd31ccd5c9..9e8fe4f617 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -111,9 +111,13 @@ namespace osu.Game.Skinning { return new DefaultSkinComponentsContainer(container => { + var leaderboard = container.OfType().FirstOrDefault(); var comboCounter = container.OfType().FirstOrDefault(); var spectatorList = container.OfType().FirstOrDefault(); + if (leaderboard != null) + leaderboard.Position = new Vector2(36, 115); + Vector2 pos = new Vector2(36, -66); if (comboCounter != null) @@ -129,6 +133,7 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + new DrawableGameplayLeaderboard(), new ArgonComboCounter { Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 51c1473303..0e782203b2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -374,6 +374,7 @@ namespace osu.Game.Skinning { var combo = container.OfType().FirstOrDefault(); var spectatorList = container.OfType().FirstOrDefault(); + var leaderboard = container.OfType().FirstOrDefault(); Vector2 pos = new Vector2(); @@ -392,10 +393,18 @@ namespace osu.Game.Skinning spectatorList.Origin = Anchor.BottomLeft; spectatorList.Position = pos; } + + if (leaderboard != null) + { + leaderboard.Anchor = Anchor.CentreLeft; + leaderboard.Origin = Anchor.CentreLeft; + leaderboard.X = 10; + } }) { new LegacyDefaultComboCounter(), new SpectatorList(), + new DrawableGameplayLeaderboard(), }; } diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index a4a967bed9..3881a5e970 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -68,10 +68,6 @@ namespace osu.Game.Skinning switch (lookup) { case GlobalSkinnableContainerLookup containerLookup: - // Only handle global level defaults for now. - if (containerLookup.Ruleset != null) - return null; - switch (containerLookup.Lookup) { case GlobalSkinnableContainers.SongSelect: @@ -83,6 +79,44 @@ namespace osu.Game.Skinning return songSelectComponents; case GlobalSkinnableContainers.MainHUDComponents: + // elements default to beneath the health bar + const float score_vertical_offset = 30; + const float horizontal_padding = 20; + + const float screen_edge_padding = 10; + + // Hard to find this at runtime, so taken from the most expanded state during replay. + const float song_progress_offset_height = 73; + + if (containerLookup.Ruleset != null) + { + return new DefaultSkinComponentsContainer(container => + { + var leaderboard = container.OfType().FirstOrDefault(); + var spectatorList = container.OfType().FirstOrDefault(); + + if (leaderboard != null) + leaderboard.Position = new Vector2(40, 60); + + if (spectatorList != null) + { + spectatorList.HeaderFont.Value = Typeface.Venera; + spectatorList.HeaderColour.Value = new OsuColour().BlueLighter; + spectatorList.Anchor = Anchor.BottomLeft; + spectatorList.Origin = Anchor.BottomLeft; + spectatorList.Position = new Vector2(screen_edge_padding, -(song_progress_offset_height + screen_edge_padding)); + } + }) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new DrawableGameplayLeaderboard(), + new SpectatorList(), + }, + }; + } + var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); @@ -91,19 +125,13 @@ namespace osu.Game.Skinning var ppCounter = container.OfType().FirstOrDefault(); var songProgress = container.OfType().FirstOrDefault(); var keyCounter = container.OfType().FirstOrDefault(); - var spectatorList = container.OfType().FirstOrDefault(); if (score != null) { score.Anchor = Anchor.TopCentre; score.Origin = Anchor.TopCentre; - // elements default to beneath the health bar - const float vertical_offset = 30; - - const float horizontal_padding = 20; - - score.Position = new Vector2(0, vertical_offset); + score.Position = new Vector2(0, score_vertical_offset); if (ppCounter != null) { @@ -114,13 +142,13 @@ namespace osu.Game.Skinning if (accuracy != null) { - accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5); + accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, score_vertical_offset + 5); accuracy.Origin = Anchor.TopRight; accuracy.Anchor = Anchor.TopCentre; if (combo != null) { - combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5); + combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, score_vertical_offset + 5); combo.Anchor = Anchor.TopCentre; } } @@ -144,25 +172,11 @@ namespace osu.Game.Skinning } } - const float padding = 10; - - // Hard to find this at runtime, so taken from the most expanded state during replay. - const float song_progress_offset_height = 73; - if (songProgress != null && keyCounter != null) { keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(-padding, -(song_progress_offset_height + padding)); - } - - if (spectatorList != null) - { - spectatorList.HeaderFont.Value = Typeface.Venera; - spectatorList.HeaderColour.Value = new OsuColour().BlueLighter; - spectatorList.Anchor = Anchor.BottomLeft; - spectatorList.Origin = Anchor.BottomLeft; - spectatorList.Position = new Vector2(padding, -(song_progress_offset_height + padding)); + keyCounter.Position = new Vector2(-screen_edge_padding, -(song_progress_offset_height + screen_edge_padding)); } }) { @@ -177,7 +191,6 @@ namespace osu.Game.Skinning new BarHitErrorMeter(), new BarHitErrorMeter(), new TrianglesPerformancePointsCounter(), - new SpectatorList(), } };