From 166992eb43fe2d12eebad9c697ad18fc04af1458 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Mar 2026 15:39:38 +0900 Subject: [PATCH 1/5] Remove unused variable --- osu.Game/Screens/Play/HUDOverlay.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 9889ff460d48..5e06b20c099f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -38,11 +38,6 @@ public partial class HUDOverlay : Container, IKeyBindingHandler public const Easing FADE_EASING = Easing.OutQuint; - /// - /// The total height of all the bottom of screen scoring elements. - /// - public float BottomScoringElementsHeight { get; private set; } - protected override bool ShouldBeConsideredForInput(Drawable child) { // HUD uses AlwaysVisible on child components so they can be in an updated state for next display. @@ -295,7 +290,7 @@ protected override void Update() TopLeftElements.Y = 0; if (highestBottomScreenSpace.HasValue && DrawHeight - BottomRightElements.DrawHeight > 0) - BottomRightElements.Y = BottomScoringElementsHeight = -Math.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - BottomRightElements.DrawHeight); + BottomRightElements.Y = -Math.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - BottomRightElements.DrawHeight); else BottomRightElements.Y = 0; From d00003fb89cc782d4cec3016b2c051380910d5df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Mar 2026 16:29:28 +0900 Subject: [PATCH 2/5] Move replay overlays out of `HUD` and `Player` --- .../Visual/Gameplay/TestSceneReplayPlayer.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../TestSceneSkinEditorNavigation.cs | 2 +- .../Spectate/MultiSpectatorPlayer.cs | 3 +- .../Spectate/MultiSpectatorScreen.cs | 8 +- osu.Game/Screens/Play/HUD/ReplayOverlay.cs | 86 +++++++++++++++++++ ...ngsOverlay.cs => ReplaySettingsOverlay.cs} | 20 ++--- osu.Game/Screens/Play/HUDOverlay.cs | 28 +----- osu.Game/Screens/Play/Player.cs | 10 +-- osu.Game/Screens/Play/ReplayPlayer.cs | 48 ++++++----- osu.Game/Screens/Play/SpectatorPlayer.cs | 40 +++++---- 11 files changed, 159 insertions(+), 90 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/ReplayOverlay.cs rename osu.Game/Screens/Play/HUD/{PlayerSettingsOverlay.cs => ReplaySettingsOverlay.cs} (89%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs index 6be8f7d1858b..c360dc6b5090 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs @@ -211,7 +211,7 @@ public void TestPlayerLoaderSettingsHover() AddStep("move mouse to centre of screen", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for settings overlay hidden", () => settingsOverlay().Expanded.Value, () => Is.False); - PlayerSettingsOverlay settingsOverlay() => Player.ChildrenOfType().Single(); + ReplaySettingsOverlay settingsOverlay() => Player.ChildrenOfType().Single(); } private void loadPlayerWithBeatmap(IBeatmap? beatmap = null) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 0a042d189d22..57baaecc39f2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -156,7 +156,7 @@ public void TestSpectatorPlayerInteractiveElementsHidden() // components wrapped in skinnable target containers load asynchronously, potentially taking more than one frame to load. // therefore use until step rather than direct assert to account for that. AddUntilStep("all interactive elements removed", () => this.ChildrenOfType().All(p => - !p.ChildrenOfType().Any() && + !p.ChildrenOfType().Any() && !p.ChildrenOfType().Any() && p.ChildrenOfType().SingleOrDefault()?.Interactive == false)); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 97dfcd75b584..d96fe4facc1f 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -300,7 +300,7 @@ public void TestGameplaySettingsDoesNotExpandWhenSkinOverlayPresent() AddStep("move cursor to right of screen too far", () => InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopRight + new Vector2(10240, 0))); AddUntilStep("settings not visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.EqualTo(0)); - PlayerSettingsOverlay getPlayerSettingsOverlay() => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType().SingleOrDefault(); + ReplaySettingsOverlay getPlayerSettingsOverlay() => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType().SingleOrDefault(); } [Test] diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 070cda327ac2..debc83052f43 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -39,6 +39,8 @@ public MultiSpectatorPlayer(Score score, SpectatorPlayerClock spectatorPlayerClo : base(score, new PlayerConfiguration { AllowUserInteraction = false }) { this.spectatorPlayerClock = spectatorPlayerClock; + + ShowSettingsOverlay = false; } [BackgroundDependencyLoader] @@ -54,7 +56,6 @@ private void load(CancellationToken cancellationToken) // also applied in `MultiplayerPlayer.load()` ScoreProcessor.ApplyNewJudgementsWhenFailed = true; - HUDOverlay.PlayerSettingsOverlay.Expire(); HUDOverlay.HoldToQuit.Expire(); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index ad7966587e5b..c5309a17f7e9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -65,7 +65,7 @@ public partial class MultiSpectatorScreen : SpectatorScreen private readonly Room room; - private PlayerSettingsOverlay playerSettingsOverlay = null!; + private ReplaySettingsOverlay replaySettingsOverlay = null!; private Bindable configSettingsOverlay = null!; /// @@ -138,7 +138,7 @@ private void load(OsuConfigManager config) { ReadyToStart = performInitialSeek, }, - playerSettingsOverlay = new PlayerSettingsOverlay + replaySettingsOverlay = new ReplaySettingsOverlay { Alpha = 0, } @@ -189,9 +189,9 @@ protected override void LoadComplete() private void updateVisibility() { if (configSettingsOverlay.Value) - playerSettingsOverlay.Show(); + replaySettingsOverlay.Show(); else - playerSettingsOverlay.Hide(); + replaySettingsOverlay.Hide(); } protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/ReplayOverlay.cs b/osu.Game/Screens/Play/HUD/ReplayOverlay.cs new file mode 100644 index 000000000000..6dd9494309a9 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ReplayOverlay.cs @@ -0,0 +1,86 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Input.Bindings; + +namespace osu.Game.Screens.Play.HUD +{ + public partial class ReplayOverlay : CompositeDrawable, IKeyBindingHandler + { + public ReplaySettingsOverlay Settings { get; private set; } = null!; + + private const int fade_duration = 200; + + private Bindable configSettingsOverlay = null!; + private Container messageContainer = null!; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; + + configSettingsOverlay = config.GetBindable(OsuSetting.ReplaySettingsOverlay); + + InternalChild = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + messageContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, + }, + Settings = new ReplaySettingsOverlay(), + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + configSettingsOverlay.BindValueChanged(_ => updateVisibility(), true); + } + + private void updateVisibility() + { + if (configSettingsOverlay.Value) + Show(); + else + Hide(); + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.ToggleReplaySettings: + configSettingsOverlay.Value = !configSettingsOverlay.Value; + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + + public override void Show() => this.FadeIn(fade_duration, Easing.OutQuint); + public override void Hide() => this.FadeOut(fade_duration, Easing.OutQuint); + + public void SetMessage(ScrollingMessage scrollingMessage) => messageContainer.Child = scrollingMessage; + } +} diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs similarity index 89% rename from osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs rename to osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs index 4fe207a6f901..272bd599c7d8 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD { - public partial class PlayerSettingsOverlay : ExpandingContainer + public partial class ReplaySettingsOverlay : ExpandingContainer { private const float padding = 10; @@ -27,11 +27,6 @@ public partial class PlayerSettingsOverlay : ExpandingContainer private const float player_settings_width = 270; - private const int fade_duration = 200; - - public override void Show() => this.FadeIn(fade_duration); - public override void Hide() => this.FadeOut(fade_duration); - // we'll handle this ourselves because we have slightly custom logic. protected override bool ExpandOnHover => false; @@ -52,7 +47,7 @@ public partial class PlayerSettingsOverlay : ExpandingContainer // while collapsed down, so let's avoid that. protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; - public PlayerSettingsOverlay() + public ReplaySettingsOverlay() : base(0, EXPANDED_WIDTH) { Origin = Anchor.TopRight; @@ -81,11 +76,14 @@ public PlayerSettingsOverlay() Action = () => Expanded.Toggle() }); - AddInternal(new Box + AddRangeInternal(new Drawable[] { - Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), Color4.Black.Opacity(0.8f)), - Depth = float.MaxValue, - RelativeSizeAxes = Axes.Both, + new Box + { + Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), Color4.Black.Opacity(0.8f)), + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, }); } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 5e06b20c099f..d913673fc8cd 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -51,7 +51,6 @@ protected override bool ShouldBeConsideredForInput(Drawable child) public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; - public readonly PlayerSettingsOverlay PlayerSettingsOverlay; [Cached] private readonly ClicksPerSecondController clicksPerSecondController; @@ -77,7 +76,6 @@ protected override bool ShouldBeConsideredForInput(Drawable child) private Bindable configVisibilityMode; private Bindable configLeaderboardVisibility; - private Bindable configSettingsOverlay; private readonly BindableBool replayLoaded = new BindableBool(); @@ -111,8 +109,6 @@ protected override bool ShouldBeConsideredForInput(Drawable child) public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, PlayerConfiguration configuration) { - Container rightSettings; - this.drawableRuleset = drawableRuleset; this.mods = mods; this.configuration = configuration; @@ -165,17 +161,6 @@ public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList { mainComponents, TopRightElements, rightSettings }; + hideTargets = new List { mainComponents, TopRightElements }; if (rulesetComponents != null) hideTargets.Add(rulesetComponents); @@ -205,7 +190,6 @@ private void load(OsuConfigManager config, RealmKeyBindingStore keyBindingStore, configVisibilityMode = config.GetBindable(OsuSetting.HUDVisibilityMode); configLeaderboardVisibility = config.GetBindable(OsuSetting.GameplayLeaderboard); - configSettingsOverlay = config.GetBindable(OsuSetting.ReplaySettingsOverlay); if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce) { @@ -233,7 +217,6 @@ protected override void LoadComplete() holdingForHUD.BindValueChanged(_ => updateVisibility()); IsPlaying.BindValueChanged(_ => updateVisibility()); configVisibilityMode.BindValueChanged(_ => updateVisibility()); - configSettingsOverlay.BindValueChanged(_ => updateVisibility()); replayLoaded.BindValueChanged(e => { @@ -344,11 +327,6 @@ void processDrawable(ISerialisableDrawable element) private void updateVisibility() { - if (configSettingsOverlay.Value && replayLoaded.Value) - PlayerSettingsOverlay.Show(); - else - PlayerSettingsOverlay.Hide(); - if (ShowHud.Disabled) return; @@ -410,10 +388,6 @@ public bool OnPressed(KeyBindingPressEvent e) switch (e.Action) { - case GlobalAction.ToggleReplaySettings: - configSettingsOverlay.Value = !configSettingsOverlay.Value; - return true; - case GlobalAction.HoldForHUD: holdingForHUD.Value = true; return false; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 97c0a0b7690e..8a9655bf2bb8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -322,6 +322,7 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c }, exitOverlay = new HotkeyExitOverlay { + Depth = float.MinValue, Action = () => { if (!this.IsCurrentScreen()) return; @@ -361,6 +362,9 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. failAnimationContainer.Add(createOverlayComponents()); + // Used by ReplaySettingsOverlay for button positioning. + dependencies.CacheAs(HUDOverlay); + if (!DrawableRuleset.AllowGameplayOverlays) { HUDOverlay.ShowHud.Value = false; @@ -426,11 +430,6 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } - /// - /// Implement to add any components which should exist above gameplay but below the HUD. - /// - protected virtual Drawable CreateOverlayComponents() => Empty(); - protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); private Drawable createUnderlayComponents(WorkingBeatmap working) @@ -479,7 +478,6 @@ private Drawable createOverlayComponents() Children = new[] { DimmableStoryboard.OverlayLayerContainer.CreateProxy(), - CreateOverlayComponents(), HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods, Configuration) { HoldToQuit = diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index e3c0361052b3..a5ecdb232059 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -17,6 +17,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.Leaderboards; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking; @@ -47,6 +48,8 @@ public partial class ReplayPlayer : Player, IKeyBindingHandler private ReplayFailIndicator? failIndicator; private PlaybackSettings? playbackSettings; + private ReplayOverlay replayOverlay = null!; + protected override bool CheckModsAllowFailure() { // autoplay should be able to fail if the beatmap is not humanly beatable @@ -80,7 +83,7 @@ public ReplayPlayer(Func, Score> createScore, Playe /// Add a settings group to the HUD overlay. Intended to be used by rulesets to add replay-specific settings. /// /// The settings group to be shown. - public void AddSettings(PlayerSettingsGroup settings) => Schedule(() => HUDOverlay.PlayerSettingsOverlay.Add(settings)); + public void AddSettings(PlayerSettingsGroup settings) => Schedule(() => replayOverlay.Settings.Add(settings)); [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -90,6 +93,8 @@ private void load(OsuConfigManager config) AddInternal(leaderboardProvider); + GameplayClockContainer.Add(replayOverlay = new ReplayOverlay()); + playbackSettings = new PlaybackSettings { Depth = float.MaxValue, @@ -99,26 +104,8 @@ private void load(OsuConfigManager config) if (GameplayClockContainer is MasterGameplayClockContainer master) playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate); - HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - - AddInternal(new RulesetSkinProvidingContainer(GameplayState.Ruleset, GameplayState.Beatmap, Beatmap.Value.Skin) - { - Child = failIndicator = new ReplayFailIndicator(GameplayClockContainer) - { - GoToResults = () => - { - if (!this.IsCurrentScreen()) - return; - - ValidForResume = false; - this.Push(new SoloResultsScreen(Score.ScoreInfo)); - } - } - }); - } + replayOverlay.Settings.AddAtStart(playbackSettings); - protected override Drawable CreateOverlayComponents() - { OsuTextFlowContainer message = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Style.Body) { AutoSizeAxes = Axes.Both }; message.AddText("Watching "); message.AddText(Score.ScoreInfo.User.Username, s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); @@ -130,12 +117,27 @@ protected override Drawable CreateOverlayComponents() Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), }); - return new ScrollingMessage(message) + replayOverlay.SetMessage(new ScrollingMessage(message) { - Y = 100, + Y = 96, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - }; + }); + + AddInternal(new RulesetSkinProvidingContainer(GameplayState.Ruleset, GameplayState.Beatmap, Beatmap.Value.Skin) + { + Child = failIndicator = new ReplayFailIndicator(GameplayClockContainer) + { + GoToResults = () => + { + if (!this.IsCurrentScreen()) + return; + + ValidForResume = false; + this.Push(new SoloResultsScreen(Score.ScoreInfo)); + } + } + }); } protected override void PrepareReplay() diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 6d008447bb68..910a4aef6583 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play @@ -23,29 +24,38 @@ public abstract partial class SpectatorPlayer : Player private readonly Score score; + public bool ShowSettingsOverlay { get; init; } = true; + protected SpectatorPlayer(Score score, PlayerConfiguration? configuration = null) : base(configuration) { this.score = score; } - protected override Drawable CreateOverlayComponents() + [BackgroundDependencyLoader] + private void load() { - // TODO: This should be customised for `MultiplayerSpectatorPlayer` to be static and only show the player name. - // Or maybe we should completely redesign this to show the user avatar and other things if that happens. - OsuTextFlowContainer message = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Style.Body) { AutoSizeAxes = Axes.Both }; - message.AddText("Watching "); - message.AddText(Score.ScoreInfo.User.Username, s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); - message.AddText(" play "); - message.AddText(Beatmap.Value.BeatmapInfo.GetDisplayTitleRomanisable(), s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); - message.AddText(" live", s => s.Font = s.Font.With(weight: FontWeight.Bold)); - - return new ScrollingMessage(message) + if (ShowSettingsOverlay) { - Y = 100, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }; + var replayOverlay = new ReplayOverlay(); + GameplayClockContainer.Add(replayOverlay); + + // TODO: This should be customised for `MultiplayerSpectatorPlayer` to be static and only show the player name. + // Or maybe we should completely redesign this to show the user avatar and other things if that happens. + OsuTextFlowContainer message = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Style.Body) { AutoSizeAxes = Axes.Both }; + message.AddText("Watching "); + message.AddText(Score.ScoreInfo.User.Username, s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); + message.AddText(" play "); + message.AddText(Beatmap.Value.BeatmapInfo.GetDisplayTitleRomanisable(), s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); + message.AddText(" live", s => s.Font = s.Font.With(weight: FontWeight.Bold)); + + replayOverlay.SetMessage(new ScrollingMessage(message) + { + Y = 96, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }); + } } protected override void LoadComplete() From 8b5acf0d9ba56eeb33ff8d915328fd057efae6ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Mar 2026 16:38:03 +0900 Subject: [PATCH 3/5] Ensure fades from player-wide overlays apply to all components by enforcing depth --- osu.Game/Screens/Play/Player.cs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8a9655bf2bb8..ff62db7e165d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -320,33 +320,32 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c OnRetry = Configuration.AllowUserInteraction ? () => Restart() : null, OnQuit = () => PerformExitWithConfirmation(), }, - exitOverlay = new HotkeyExitOverlay - { - Depth = float.MinValue, - Action = () => - { - if (!this.IsCurrentScreen()) return; - - PerformExit(skipTransition: true); - }, - }, }); if (cancellationToken.IsCancellationRequested) return; + GameplayClockContainer.Add(exitOverlay = new HotkeyExitOverlay + { + Depth = float.MinValue, + Action = () => + { + if (!this.IsCurrentScreen()) return; + + PerformExit(skipTransition: true); + }, + }); + if (Configuration.AllowRestart) { - rulesetSkinProvider.AddRange(new Drawable[] + GameplayClockContainer.Add(retryOverlay = new HotkeyRetryOverlay { - retryOverlay = new HotkeyRetryOverlay + Depth = float.MinValue, + Action = () => { - Action = () => - { - if (!this.IsCurrentScreen()) return; + if (!this.IsCurrentScreen()) return; - Restart(true); - }, + Restart(true); }, }); } From 545e19527dc053147f355ade3174fe51821037d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Apr 2026 15:40:37 +0900 Subject: [PATCH 4/5] Upddate cinema mod implementation to handle new structure --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 06cde147098f..eb7bf10bfbea 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -46,7 +46,7 @@ public void ApplyToPlayer(Player player) player.DimmableStoryboard.IgnoreUserSettings.Value = true; player.BreakOverlay.Hide(); - player.OverlayComponents.Hide(); + (player as ReplayPlayer)?.ReplayOverlay.Hide(); } public bool PerformFail() => false; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index a5ecdb232059..e2ef847c2b38 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -48,7 +48,7 @@ public partial class ReplayPlayer : Player, IKeyBindingHandler private ReplayFailIndicator? failIndicator; private PlaybackSettings? playbackSettings; - private ReplayOverlay replayOverlay = null!; + public ReplayOverlay ReplayOverlay { get; private set; } = null!; protected override bool CheckModsAllowFailure() { @@ -83,7 +83,7 @@ public ReplayPlayer(Func, Score> createScore, Playe /// Add a settings group to the HUD overlay. Intended to be used by rulesets to add replay-specific settings. /// /// The settings group to be shown. - public void AddSettings(PlayerSettingsGroup settings) => Schedule(() => replayOverlay.Settings.Add(settings)); + public void AddSettings(PlayerSettingsGroup settings) => Schedule(() => ReplayOverlay.Settings.Add(settings)); [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -93,7 +93,7 @@ private void load(OsuConfigManager config) AddInternal(leaderboardProvider); - GameplayClockContainer.Add(replayOverlay = new ReplayOverlay()); + GameplayClockContainer.Add(ReplayOverlay = new ReplayOverlay()); playbackSettings = new PlaybackSettings { @@ -104,7 +104,7 @@ private void load(OsuConfigManager config) if (GameplayClockContainer is MasterGameplayClockContainer master) playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate); - replayOverlay.Settings.AddAtStart(playbackSettings); + ReplayOverlay.Settings.AddAtStart(playbackSettings); OsuTextFlowContainer message = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Style.Body) { AutoSizeAxes = Axes.Both }; message.AddText("Watching "); @@ -117,7 +117,7 @@ private void load(OsuConfigManager config) Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), }); - replayOverlay.SetMessage(new ScrollingMessage(message) + ReplayOverlay.SetMessage(new ScrollingMessage(message) { Y = 96, Anchor = Anchor.TopCentre, From 3f60411052fac81594de63c61fae2e2a9cbefbc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Apr 2026 15:45:15 +0900 Subject: [PATCH 5/5] Fix controls always being interactive even when hidden --- osu.Game/Screens/Play/HUD/ReplayOverlay.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ReplayOverlay.cs b/osu.Game/Screens/Play/HUD/ReplayOverlay.cs index 6dd9494309a9..b59cd5fa10af 100644 --- a/osu.Game/Screens/Play/HUD/ReplayOverlay.cs +++ b/osu.Game/Screens/Play/HUD/ReplayOverlay.cs @@ -20,18 +20,18 @@ public partial class ReplayOverlay : CompositeDrawable, IKeyBindingHandler configSettingsOverlay = null!; private Container messageContainer = null!; + private Container content = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { RelativeSizeAxes = Axes.Both; - AlwaysPresent = true; - Alpha = 0; configSettingsOverlay = config.GetBindable(OsuSetting.ReplaySettingsOverlay); - InternalChild = new Container + InternalChild = content = new Container { + Alpha = 0, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -54,9 +54,9 @@ protected override void LoadComplete() private void updateVisibility() { if (configSettingsOverlay.Value) - Show(); + content.FadeIn(fade_duration, Easing.OutQuint); else - Hide(); + content.FadeOut(fade_duration, Easing.OutQuint); } public bool OnPressed(KeyBindingPressEvent e)