From d3e91024a7cb9dd52185b5f067e304ffa8df3bd9 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 8 Mar 2018 18:16:23 +0900
Subject: [PATCH 1/4] Block player enter when a drag initiates from an
 overlaying container

---
 osu.Game/Screens/Play/PlayerLoader.cs         | 19 ++++++++++++++++---
 .../PlayerSettings/PlayerSettingsGroup.cs     |  4 ++++
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 2950990779..e082e3f8de 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
 using osu.Framework.Screens;
 using osu.Game.Beatmaps;
 using osu.Game.Graphics;
@@ -22,7 +23,6 @@ namespace osu.Game.Screens.Play
         private Player player;
 
         private BeatmapMetadataDisplay info;
-        private VisualSettings visualSettings;
 
         private bool showOverlays = true;
         public override bool ShowOverlaysOnEnter => showOverlays;
@@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play
                 Anchor = Anchor.Centre,
                 Origin = Anchor.Centre,
             });
-            Add(visualSettings = new VisualSettings
+            Add(new VisualSettings
             {
                 Anchor = Anchor.TopRight,
                 Origin = Anchor.TopRight,
@@ -116,9 +116,22 @@ namespace osu.Game.Screens.Play
             logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo);
         }
 
+        private bool weHandledMouseDown;
+        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+        {
+            weHandledMouseDown = true;
+            return base.OnMouseDown(state, args);
+        }
+
+        protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+        {
+            weHandledMouseDown = false;
+            return base.OnMouseUp(state, args);
+        }
+
         private void pushWhenLoaded()
         {
-            if (player.LoadState != LoadState.Ready || visualSettings.IsHovered)
+            if (player.LoadState != LoadState.Ready || !IsHovered || GetContainingInputManager().CurrentState.Mouse.HasAnyButtonPressed && !weHandledMouseDown)
             {
                 Schedule(pushWhenLoaded);
                 return;
diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs
index 95b464154a..e0de89535e 100644
--- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Graphics.UserInterface;
@@ -133,5 +134,8 @@ namespace osu.Game.Screens.Play.PlayerSettings
         }
 
         protected override Container<Drawable> Content => content;
+
+        protected override bool OnHover(InputState state) => true;
+        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
     }
 }

From 94ed4ab01b3748a7df6c58925b5f68bb35278943 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 8 Mar 2018 20:28:55 +0900
Subject: [PATCH 2/4] Add debouncing to player loading

Allows the mouse to temporarily exit and re-enter overlay elements without triggering a load
---
 osu.Game/Screens/Play/PlayerLoader.cs | 42 +++++++++++++++++----------
 1 file changed, 27 insertions(+), 15 deletions(-)

diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index e082e3f8de..b91272de75 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites;
 using osu.Game.Screens.Backgrounds;
 using OpenTK;
 using osu.Framework.Localisation;
+using osu.Framework.Threading;
 using osu.Game.Screens.Menu;
 using osu.Game.Screens.Play.PlayerSettings;
 
@@ -51,6 +52,7 @@ namespace osu.Game.Screens.Play
                 Anchor = Anchor.Centre,
                 Origin = Anchor.Centre,
             });
+
             Add(new VisualSettings
             {
                 Anchor = Anchor.TopRight,
@@ -100,7 +102,7 @@ namespace osu.Game.Screens.Play
             contentIn();
 
             info.Delay(750).FadeIn(500);
-            this.Delay(2150).Schedule(pushWhenLoaded);
+            this.Delay(1800).Schedule(pushWhenLoaded);
         }
 
         protected override void LogoArriving(OsuLogo logo, bool resuming)
@@ -129,29 +131,39 @@ namespace osu.Game.Screens.Play
             return base.OnMouseUp(state, args);
         }
 
+        private ScheduledDelegate pushDebounce;
+
+        private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && (!GetContainingInputManager().CurrentState.Mouse.HasAnyButtonPressed || weHandledMouseDown);
+
         private void pushWhenLoaded()
         {
-            if (player.LoadState != LoadState.Ready || !IsHovered || GetContainingInputManager().CurrentState.Mouse.HasAnyButtonPressed && !weHandledMouseDown)
+            Schedule(pushWhenLoaded);
+
+            if (!readyForPush)
             {
-                Schedule(pushWhenLoaded);
+                pushDebounce?.Cancel();
+                pushDebounce = null;
                 return;
             }
 
-            contentOut();
-
-            this.Delay(250).Schedule(() =>
+            if (pushDebounce == null) pushDebounce = Scheduler.AddDelayed(() =>
             {
-                if (!IsCurrentScreen) return;
+                contentOut();
 
-                if (!Push(player))
-                    Exit();
-                else
+                this.Delay(250).Schedule(() =>
                 {
-                    //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 (!IsCurrentScreen) return;
+
+                    if (!Push(player))
+                        Exit();
+                    else
+                    {
+                        //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;
+                    }
+                });
+            }, 500);
         }
 
         protected override bool OnExiting(Screen next)

From ef8d59591445d0f18cddb4003ab7aa5ce859cf47 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 14 Mar 2018 11:44:19 +0900
Subject: [PATCH 3/4] Apply formatting changes

---
 osu.Game/Screens/Play/PlayerLoader.cs | 32 ++++++++++++++-------------
 1 file changed, 17 insertions(+), 15 deletions(-)

diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 3f25ef8a5e..cdb6f36a6f 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -112,6 +112,7 @@ namespace osu.Game.Screens.Play
         }
 
         private bool weHandledMouseDown;
+
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
         {
             weHandledMouseDown = true;
@@ -139,24 +140,25 @@ namespace osu.Game.Screens.Play
                 return;
             }
 
-            if (pushDebounce == null) pushDebounce = Scheduler.AddDelayed(() =>
-            {
-                contentOut();
-
-                this.Delay(250).Schedule(() =>
+            if (pushDebounce == null)
+                pushDebounce = Scheduler.AddDelayed(() =>
                 {
-                    if (!IsCurrentScreen) return;
+                    contentOut();
 
-                    if (!Push(player))
-                        Exit();
-                    else
+                    this.Delay(250).Schedule(() =>
                     {
-                        //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;
-                    }
-                });
-            }, 500);
+                        if (!IsCurrentScreen) return;
+
+                        if (!Push(player))
+                            Exit();
+                        else
+                        {
+                            //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;
+                        }
+                    });
+                }, 500);
         }
 
         protected override bool OnExiting(Screen next)

From ea649f96504a72be9ebbcc55c2f70b6dd32e563b Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 14 Mar 2018 12:01:15 +0900
Subject: [PATCH 4/4] Avoid scheduling during non-current screen

---
 osu.Game/Screens/Play/PlayerLoader.cs | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index cdb6f36a6f..31e7313c0b 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -131,16 +131,22 @@ namespace osu.Game.Screens.Play
 
         private void pushWhenLoaded()
         {
-            Schedule(pushWhenLoaded);
+            if (!IsCurrentScreen) return;
 
-            if (!readyForPush)
+            try
             {
-                pushDebounce?.Cancel();
-                pushDebounce = null;
-                return;
-            }
+                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.
+                    pushDebounce?.Cancel();
+                    pushDebounce = null;
+                    return;
+                }
+
+                if (pushDebounce != null)
+                    return;
 
-            if (pushDebounce == null)
                 pushDebounce = Scheduler.AddDelayed(() =>
                 {
                     contentOut();
@@ -159,6 +165,11 @@ namespace osu.Game.Screens.Play
                         }
                     });
                 }, 500);
+            }
+            finally
+            {
+                Schedule(pushWhenLoaded);
+            }
         }
 
         protected override bool OnExiting(Screen next)