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/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/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..b59cd5fa10af --- /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!; + private Container content = null!; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + RelativeSizeAxes = Axes.Both; + + configSettingsOverlay = config.GetBindable(OsuSetting.ReplaySettingsOverlay); + + InternalChild = content = new Container + { + Alpha = 0, + 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) + content.FadeIn(fade_duration, Easing.OutQuint); + else + content.FadeOut(fade_duration, Easing.OutQuint); + } + + 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 9889ff460d48..d913673fc8cd 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. @@ -56,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; @@ -82,7 +76,6 @@ protected override bool ShouldBeConsideredForInput(Drawable child) private Bindable configVisibilityMode; private Bindable configLeaderboardVisibility; - private Bindable configSettingsOverlay; private readonly BindableBool replayLoaded = new BindableBool(); @@ -116,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; @@ -170,17 +161,6 @@ public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList { mainComponents, TopRightElements, rightSettings }; + hideTargets = new List { mainComponents, TopRightElements }; if (rulesetComponents != null) hideTargets.Add(rulesetComponents); @@ -210,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) { @@ -238,7 +217,6 @@ protected override void LoadComplete() holdingForHUD.BindValueChanged(_ => updateVisibility()); IsPlaying.BindValueChanged(_ => updateVisibility()); configVisibilityMode.BindValueChanged(_ => updateVisibility()); - configSettingsOverlay.BindValueChanged(_ => updateVisibility()); replayLoaded.BindValueChanged(e => { @@ -295,7 +273,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; @@ -349,11 +327,6 @@ void processDrawable(ISerialisableDrawable element) private void updateVisibility() { - if (configSettingsOverlay.Value && replayLoaded.Value) - PlayerSettingsOverlay.Show(); - else - PlayerSettingsOverlay.Hide(); - if (ShowHud.Disabled) return; @@ -415,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 f8530621e371..ff62db7e165d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -320,32 +320,32 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c OnRetry = Configuration.AllowUserInteraction ? () => Restart() : null, OnQuit = () => PerformExitWithConfirmation(), }, - exitOverlay = new HotkeyExitOverlay - { - 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); }, }); } @@ -361,6 +361,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,16 +429,6 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } - /// - /// Components which were created via . - /// - public Drawable OverlayComponents { get; private set; } - - /// - /// 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) @@ -484,7 +477,6 @@ private Drawable createOverlayComponents() Children = new[] { DimmableStoryboard.OverlayLayerContainer.CreateProxy(), - OverlayComponents = 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..e2ef847c2b38 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; + public ReplayOverlay ReplayOverlay { get; private set; } = 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()