diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index d971e777ec..6286c80d7c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using static osu.Game.Input.Handlers.ReplayInputHandler; @@ -24,6 +25,21 @@ namespace osu.Game.Rulesets.Osu.Mods /// private const float relax_leniency = 3; + private bool isDownState; + private bool wasLeft; + + private OsuInputManager osuInputManager; + + private ReplayState state; + private double lastStateChangeTime; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // grab the input manager for future use. + osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + osuInputManager.AllowUserPresses = false; + } + public void Update(Playfield playfield) { bool requiresHold = false; @@ -63,11 +79,14 @@ namespace osu.Game.Rulesets.Osu.Mods if (requiresHit) { - addAction(false); - addAction(true); + changeState(false); + changeState(true); } - addAction(requiresHold); + if (requiresHold) + changeState(true); + else if (isDownState && time - lastStateChangeTime > AutoGenerator.KEY_UP_DELAY) + changeState(false); void handleHitCircle(DrawableHitCircle circle) { @@ -77,39 +96,28 @@ namespace osu.Game.Rulesets.Osu.Mods Debug.Assert(circle.HitObject.HitWindows != null); requiresHit |= circle.HitObject.HitWindows.CanBeHit(time - circle.HitObject.StartTime); } - } - private bool wasHit; - private bool wasLeft; - - private OsuInputManager osuInputManager; - - private void addAction(bool hitting) - { - if (wasHit == hitting) - return; - - wasHit = hitting; - - var state = new ReplayState + void changeState(bool down) { - PressedActions = new List() - }; + if (isDownState == down) + return; - if (hitting) - { - state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); - wasLeft = !wasLeft; + isDownState = down; + lastStateChangeTime = time; + + state = new ReplayState + { + PressedActions = new List() + }; + + if (down) + { + state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + wasLeft = !wasLeft; + } + + state?.Apply(osuInputManager.CurrentState, osuInputManager); } - - state.Apply(osuInputManager.CurrentState, osuInputManager); - } - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - // grab the input manager for future use. - osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - osuInputManager.AllowUserPresses = false; } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 33ecbed62e..100f99d130 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -91,9 +91,44 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("load dummy beatmap", () => ResetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20); + + AddUntilStep("wait for load ready", () => + { + moveMouse(); + return player.LoadState == LoadState.Ready; + }); + AddRepeatStep("move mouse", moveMouse, 20); + AddAssert("loader still active", () => loader.IsCurrentScreen()); AddUntilStep("loads after idle", () => !loader.IsCurrentScreen()); + + void moveMouse() + { + InputManager.MoveMouseTo( + loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) + * RNG.NextSingle()); + } + } + + [Test] + public void TestBlockLoadViaFocus() + { + OsuFocusedOverlayContainer overlay = null; + + AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddStep("show focused overlay", () => { container.Add(overlay = new ChangelogOverlay { State = { Value = Visibility.Visible } }); }); + AddUntilStep("overlay visible", () => overlay.IsPresent); + + AddUntilStep("wait for load ready", () => player.LoadState == LoadState.Ready); + AddRepeatStep("twiddle thumbs", () => { }, 20); + + AddAssert("loader still active", () => loader.IsCurrentScreen()); + + AddStep("hide overlay", () => overlay.Hide()); + AddUntilStep("loads after idle", () => !loader.IsCurrentScreen()); } [Test] @@ -159,13 +194,22 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestMutedNotificationMasterVolume() => addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault); + public void TestMutedNotificationMasterVolume() + { + addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault); + } [Test] - public void TestMutedNotificationTrackVolume() => addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, null, () => audioManager.VolumeTrack.IsDefault); + public void TestMutedNotificationTrackVolume() + { + addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, null, () => audioManager.VolumeTrack.IsDefault); + } [Test] - public void TestMutedNotificationMuteButton() => addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value); + public void TestMutedNotificationMuteButton() + { + addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value); + } /// /// Created for avoiding copy pasting code for the same steps. @@ -179,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).Value = false); AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad)); - AddUntilStep("wait for player", () => player.IsLoaded); + AddUntilStep("wait for player", () => player.LoadState == LoadState.Ready); AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1); AddStep("click notification", () => @@ -193,6 +237,8 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddAssert("check " + volumeName, assert); + + AddUntilStep("wait for player load", () => player.IsLoaded); } private class TestPlayerLoaderContainer : Container diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 8d2e4a614d..70d71d0952 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -117,6 +117,8 @@ namespace osu.Game.Tests.Visual.Navigation { base.LoadComplete(); API.Login("Rhythm Champion", "osu!"); + + Dependencies.Get().Set(Static.MutedAudioNotificationShownOnce, true); } } diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 3319f30a6f..b3c609f2f4 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Replays #region Constants // Shared amongst all modes - protected const double KEY_UP_DELAY = 50; + public const double KEY_UP_DELAY = 50; #endregion diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index b1e69e4c4f..1c35d8a53f 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Multi { base.LoadComplete(); - SelectedItem.BindValueChanged(selected => maskingContainer.BorderThickness = selected.NewValue == Model ? 5 : 0); + SelectedItem.BindValueChanged(selected => maskingContainer.BorderThickness = selected.NewValue == Model ? 5 : 0, true); beatmap.BindValueChanged(_ => scheduleRefresh()); ruleset.BindValueChanged(_ => scheduleRefresh()); diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs new file mode 100644 index 0000000000..074341226e --- /dev/null +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -0,0 +1,180 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + /// + /// Displays beatmap metadata inside + /// + public class BeatmapMetadataDisplay : Container + { + private class MetadataLine : Container + { + public MetadataLine(string left, string right) + { + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight, + Margin = new MarginPadding { Right = 5 }, + Colour = OsuColour.Gray(0.8f), + Text = left, + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopLeft, + Margin = new MarginPadding { Left = 5 }, + Text = string.IsNullOrEmpty(right) ? @"-" : right, + } + }; + } + } + + private readonly WorkingBeatmap beatmap; + private readonly Bindable> mods; + private readonly Drawable facade; + private LoadingAnimation loading; + private Sprite backgroundSprite; + + public IBindable> Mods => mods; + + public bool Loading + { + set + { + if (value) + { + loading.Show(); + backgroundSprite.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); + } + else + { + loading.Hide(); + backgroundSprite.FadeColour(Color4.White, 400, Easing.OutQuint); + } + } + } + + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, Drawable facade) + { + this.beatmap = beatmap; + this.facade = facade; + + this.mods = new Bindable>(); + this.mods.BindTo(mods); + } + + [BackgroundDependencyLoader] + private void load() + { + var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); + + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Children = new[] + { + facade.With(d => + { + d.Anchor = Anchor.TopCentre; + d.Origin = Anchor.TopCentre; + }), + new OsuSpriteText + { + Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), + Font = OsuFont.GetFont(size: 36, italics: true), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Margin = new MarginPadding { Top = 15 }, + }, + new OsuSpriteText + { + Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), + Font = OsuFont.GetFont(size: 26, italics: true), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + new Container + { + Size = new Vector2(300, 60), + Margin = new MarginPadding(10), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + CornerRadius = 10, + Masking = true, + Children = new Drawable[] + { + backgroundSprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = beatmap?.Background, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + FillMode = FillMode.Fill, + }, + loading = new LoadingAnimation { Scale = new Vector2(1.3f) } + } + }, + new OsuSpriteText + { + Text = beatmap?.BeatmapInfo?.Version, + Font = OsuFont.GetFont(size: 26, italics: true), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Margin = new MarginPadding + { + Bottom = 40 + }, + }, + new MetadataLine("Source", metadata.Source) + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + new MetadataLine("Mapper", metadata.AuthorString) + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + new ModDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 20 }, + Current = mods + } + }, + } + }; + + Loading = true; + } + } +} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index f37faac988..c0d88feda2 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -12,21 +11,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; -using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; -using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Users; using osuTK; @@ -38,30 +31,65 @@ namespace osu.Game.Screens.Play { protected const float BACKGROUND_BLUR = 15; + public override bool HideOverlaysOnEnter => hideOverlays; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + // Here because IsHovered will not update unless we do so. + public override bool HandlePositionalInput => true; + + // We show the previous screen status + protected override UserActivity InitialActivity => null; + + protected override bool PlayResumeSound => false; + + protected BeatmapMetadataDisplay MetadataInfo; + + protected VisualSettings VisualSettings; + + protected Task LoadTask { get; private set; } + + protected Task DisposalTask { get; private set; } + + private bool backgroundBrightnessReduction; + + protected bool BackgroundBrightnessReduction + { + set + { + if (value == backgroundBrightnessReduction) + return; + + backgroundBrightnessReduction = value; + + Background.FadeColour(OsuColour.Gray(backgroundBrightnessReduction ? 0.8f : 1), 200); + } + } + + private bool readyForPush => + // don't push unless the player is completely loaded + player.LoadState == LoadState.Ready + // don't push if the user is hovering one of the panes, unless they are idle. + && (IsHovered || idleTracker.IsIdle.Value) + // don't push if the user is dragging a slider or otherwise. + && inputManager?.DraggedDrawable == null + // don't push if a focused overlay is visible, like settings. + && inputManager?.FocusedDrawable == null; + private readonly Func createPlayer; private Player player; private LogoTrackingContainer content; - protected BeatmapMetadataDisplay MetadataInfo; - private bool hideOverlays; - public override bool HideOverlaysOnEnter => hideOverlays; - - protected override UserActivity InitialActivity => null; //shows the previous screen status - - public override bool DisallowExternalBeatmapRulesetChanges => true; - - protected override bool PlayResumeSound => false; - - protected Task LoadTask { get; private set; } - - protected Task DisposalTask { get; private set; } private InputManager inputManager; + private IdleTracker idleTracker; + private ScheduledDelegate scheduledPushPlayer; + [Resolved(CanBeNull = true)] private NotificationOverlay notificationOverlay { get; set; } @@ -71,19 +99,11 @@ namespace osu.Game.Screens.Play [Resolved] private AudioManager audioManager { get; set; } - private Bindable muteWarningShownOnce; - public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; } - private void restartRequested() - { - hideOverlays = true; - ValidForResume = true; - } - [BackgroundDependencyLoader] private void load(SessionStatics sessionStatics) { @@ -127,11 +147,13 @@ namespace osu.Game.Screens.Play inputManager = GetContainingInputManager(); } + #region Screen handling + public override void OnEntering(IScreen last) { base.OnEntering(last); - loadNewPlayer(); + prepareNewPlayer(); content.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); @@ -141,15 +163,7 @@ namespace osu.Game.Screens.Play MetadataInfo.Delay(750).FadeIn(500); this.Delay(1800).Schedule(pushWhenLoaded); - if (!muteWarningShownOnce.Value) - { - //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) - { - notificationOverlay?.Post(new MutedNotification()); - muteWarningShownOnce.Value = true; - } - } + showMuteWarningIfNeeded(); } public override void OnResuming(IScreen last) @@ -160,36 +174,32 @@ namespace osu.Game.Screens.Play MetadataInfo.Loading = true; - //we will only be resumed if the player has requested a re-run (see ValidForResume setting above) - loadNewPlayer(); + // we will only be resumed if the player has requested a re-run (see restartRequested). + prepareNewPlayer(); this.Delay(400).Schedule(pushWhenLoaded); } - private void loadNewPlayer() + public override void OnSuspending(IScreen next) { - var restartCount = player?.RestartCount + 1 ?? 0; + base.OnSuspending(next); - player = createPlayer(); - player.RestartCount = restartCount; - player.RestartRequested = restartRequested; + cancelLoad(); - LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false); + BackgroundBrightnessReduction = false; } - private void contentIn() + public override bool OnExiting(IScreen next) { - content.ScaleTo(1, 650, Easing.OutQuint); - content.FadeInFromZero(400); - } + cancelLoad(); - private void contentOut() - { - // Ensure the logo is no longer tracking before we scale the content - content.StopTracking(); + content.ScaleTo(0.7f, 150, Easing.InQuint); + this.FadeOut(150); - content.ScaleTo(0.7f, 300, Easing.InQuint); - content.FadeOut(250); + Background.EnableUserDim.Value = false; + BackgroundBrightnessReduction = false; + + return base.OnExiting(next); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -198,10 +208,7 @@ namespace osu.Game.Screens.Play const double duration = 300; - if (!resuming) - { - logo.MoveTo(new Vector2(0.5f), duration, Easing.In); - } + if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); logo.FadeIn(350); @@ -219,109 +226,7 @@ namespace osu.Game.Screens.Play content.StopTracking(); } - private ScheduledDelegate pushDebounce; - protected VisualSettings VisualSettings; - - // Here because IsHovered will not update unless we do so. - public override bool HandlePositionalInput => true; - - private bool readyForPush => player.LoadState == LoadState.Ready && (IsHovered || idleTracker.IsIdle.Value) && inputManager?.DraggedDrawable == null; - - private void pushWhenLoaded() - { - if (!this.IsCurrentScreen()) return; - - try - { - if (!readyForPush) - { - // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce - // if we become unready for push during the delay. - cancelLoad(); - return; - } - - if (pushDebounce != null) - return; - - pushDebounce = Scheduler.AddDelayed(() => - { - contentOut(); - - this.Delay(250).Schedule(() => - { - if (!this.IsCurrentScreen()) return; - - LoadTask = null; - - //By default, we want to load the player and never be returned to. - //Note that this may change if the player we load requested a re-run. - ValidForResume = false; - - if (player.LoadedBeatmapSuccessfully) - this.Push(player); - else - this.Exit(); - }); - }, 500); - } - finally - { - Schedule(pushWhenLoaded); - } - } - - private void cancelLoad() - { - pushDebounce?.Cancel(); - pushDebounce = null; - } - - public override void OnSuspending(IScreen next) - { - BackgroundBrightnessReduction = false; - base.OnSuspending(next); - cancelLoad(); - } - - public override bool OnExiting(IScreen next) - { - content.ScaleTo(0.7f, 150, Easing.InQuint); - this.FadeOut(150); - cancelLoad(); - - Background.EnableUserDim.Value = false; - BackgroundBrightnessReduction = false; - - return base.OnExiting(next); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (isDisposing) - { - // if the player never got pushed, we should explicitly dispose it. - DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose()); - } - } - - private bool backgroundBrightnessReduction; - - protected bool BackgroundBrightnessReduction - { - get => backgroundBrightnessReduction; - set - { - if (value == backgroundBrightnessReduction) - return; - - backgroundBrightnessReduction = value; - - Background.FadeColour(OsuColour.Gray(backgroundBrightnessReduction ? 0.8f : 1), 200); - } - } + #endregion protected override void Update() { @@ -350,171 +255,129 @@ namespace osu.Game.Screens.Play } } - protected class BeatmapMetadataDisplay : Container + private void prepareNewPlayer() { - private class MetadataLine : Container + var restartCount = player?.RestartCount + 1 ?? 0; + + player = createPlayer(); + player.RestartCount = restartCount; + player.RestartRequested = restartRequested; + + LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false); + } + + private void restartRequested() + { + hideOverlays = true; + ValidForResume = true; + } + + private void contentIn() + { + content.ScaleTo(1, 650, Easing.OutQuint); + content.FadeInFromZero(400); + } + + private void contentOut() + { + // Ensure the logo is no longer tracking before we scale the content + content.StopTracking(); + + content.ScaleTo(0.7f, 300, Easing.InQuint); + content.FadeOut(250); + } + + private void pushWhenLoaded() + { + if (!this.IsCurrentScreen()) return; + + try { - public MetadataLine(string left, string right) + if (!readyForPush) { - AutoSizeAxes = Axes.Both; - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopRight, - Margin = new MarginPadding { Right = 5 }, - Colour = OsuColour.Gray(0.8f), - Text = left, - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopLeft, - Margin = new MarginPadding { Left = 5 }, - Text = string.IsNullOrEmpty(right) ? @"-" : right, - } - }; + // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce + // if we become unready for push during the delay. + cancelLoad(); + return; } - } - private readonly WorkingBeatmap beatmap; - private readonly Bindable> mods; - private readonly Drawable facade; - private LoadingAnimation loading; - private Sprite backgroundSprite; + if (scheduledPushPlayer != null) + return; - public IBindable> Mods => mods; - - public bool Loading - { - set + scheduledPushPlayer = Scheduler.AddDelayed(() => { - if (value) + contentOut(); + + this.Delay(250).Schedule(() => { - loading.Show(); - backgroundSprite.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); - } - else - { - loading.Hide(); - backgroundSprite.FadeColour(Color4.White, 400, Easing.OutQuint); - } + if (!this.IsCurrentScreen()) return; + + LoadTask = null; + + //By default, we want to load the player and never be returned to. + //Note that this may change if the player we load requested a re-run. + ValidForResume = false; + + if (player.LoadedBeatmapSuccessfully) + this.Push(player); + else + this.Exit(); + }); + }, 500); + } + finally + { + Schedule(pushWhenLoaded); + } + } + + private void cancelLoad() + { + scheduledPushPlayer?.Cancel(); + scheduledPushPlayer = null; + } + + #region Disposal + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (isDisposing) + { + // if the player never got pushed, we should explicitly dispose it. + DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose()); + } + } + + #endregion + + #region Mute warning + + private Bindable muteWarningShownOnce; + + private void showMuteWarningIfNeeded() + { + if (!muteWarningShownOnce.Value) + { + //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. + if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) + { + notificationOverlay?.Post(new MutedNotification()); + muteWarningShownOnce.Value = true; } } - - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, Drawable facade) - { - this.beatmap = beatmap; - this.facade = facade; - - this.mods = new Bindable>(); - this.mods.BindTo(mods); - } - - [BackgroundDependencyLoader] - private void load() - { - var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); - - AutoSizeAxes = Axes.Both; - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Direction = FillDirection.Vertical, - Children = new[] - { - facade.With(d => - { - d.Anchor = Anchor.TopCentre; - d.Origin = Anchor.TopCentre; - }), - new OsuSpriteText - { - Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), - Font = OsuFont.GetFont(size: 36, italics: true), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Margin = new MarginPadding { Top = 15 }, - }, - new OsuSpriteText - { - Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), - Font = OsuFont.GetFont(size: 26, italics: true), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - }, - new Container - { - Size = new Vector2(300, 60), - Margin = new MarginPadding(10), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - CornerRadius = 10, - Masking = true, - Children = new Drawable[] - { - backgroundSprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - Texture = beatmap?.Background, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - FillMode = FillMode.Fill, - }, - loading = new LoadingAnimation { Scale = new Vector2(1.3f) } - } - }, - new OsuSpriteText - { - Text = beatmap?.BeatmapInfo?.Version, - Font = OsuFont.GetFont(size: 26, italics: true), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Margin = new MarginPadding - { - Bottom = 40 - }, - }, - new MetadataLine("Source", metadata.Source) - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - }, - new MetadataLine("Mapper", metadata.AuthorString) - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - }, - new ModDisplay - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 20 }, - Current = mods - } - }, - } - }; - - Loading = true; - } } private class MutedNotification : SimpleNotification { + public override bool IsImportant => true; + public MutedNotification() { Text = "Your music volume is set to 0%! Click here to restore it."; } - public override bool IsImportant => true; - [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay) { @@ -533,5 +396,7 @@ namespace osu.Game.Screens.Play }; } } + + #endregion } }