From 26bb3715fecd3ca4e3f43e77ad7fc6075e7f4f1f Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 22 Dec 2017 22:41:18 +0900
Subject: [PATCH 1/3] Improve state and feel of leaderboard placeholders

Closes #1721.
---
 .../Select/Leaderboards/Leaderboard.cs        | 52 ++++++++++---------
 1 file changed, 28 insertions(+), 24 deletions(-)

diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
index b3f2649ab6..4b5eefe5b6 100644
--- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Leaderboards
 {
     public class Leaderboard : Container
     {
-        private const double fade_duration = 200;
+        private const double fade_duration = 300;
 
         private readonly ScrollContainer scrollContainer;
         private readonly Container placeholderContainer;
@@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select.Leaderboards
             {
                 scores = value;
 
-                scrollFlow?.FadeOut(fade_duration).Expire();
+                scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire();
                 scrollFlow = null;
 
                 loading.Hide();
@@ -90,6 +90,7 @@ namespace osu.Game.Screens.Select.Leaderboards
         }
 
         private LeaderboardScope scope;
+
         public LeaderboardScope Scope
         {
             get { return scope; }
@@ -103,6 +104,7 @@ namespace osu.Game.Screens.Select.Leaderboards
         }
 
         private PlaceholderState placeholderState;
+
         protected PlaceholderState PlaceholderState
         {
             get { return placeholderState; }
@@ -118,19 +120,18 @@ namespace osu.Game.Screens.Select.Leaderboards
                             OnRetry = updateScores,
                         });
                         break;
-
+                    case PlaceholderState.Unavailable:
+                        replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"));
+                        break;
                     case PlaceholderState.NoScores:
                         replacePlaceholder(new MessagePlaceholder(@"No records yet!"));
                         break;
-
                     case PlaceholderState.NotLoggedIn:
                         replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!"));
                         break;
-
                     case PlaceholderState.NotSupporter:
                         replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!"));
                         break;
-
                     default:
                         replacePlaceholder(null);
                         break;
@@ -150,10 +151,7 @@ namespace osu.Game.Screens.Select.Leaderboards
                 loading = new LoadingAnimation(),
                 placeholderContainer = new Container
                 {
-                    Alpha = 0,
-                    AutoSizeAxes = Axes.Both,
-                    Anchor = Anchor.Centre,
-                    Origin = Anchor.Centre,
+                    RelativeSizeAxes = Axes.Both
                 },
             };
         }
@@ -229,15 +227,15 @@ namespace osu.Game.Screens.Select.Leaderboards
                 return;
             }
 
-            if (api?.IsLoggedIn != true)
+            if (Beatmap?.OnlineBeatmapID == null)
             {
-                PlaceholderState = PlaceholderState.NotLoggedIn;
+                PlaceholderState = PlaceholderState.Unavailable;
                 return;
             }
 
-            if (Beatmap?.OnlineBeatmapID == null)
+            if (api?.IsLoggedIn != true)
             {
-                PlaceholderState = PlaceholderState.NetworkFailure;
+                PlaceholderState = PlaceholderState.NotLoggedIn;
                 return;
             }
 
@@ -272,23 +270,22 @@ namespace osu.Game.Screens.Select.Leaderboards
 
         private void replacePlaceholder(Placeholder placeholder)
         {
-            if (placeholder == null)
-            {
-                placeholderContainer.FadeOutFromOne(fade_duration, Easing.OutQuint);
-                placeholderContainer.Clear(true);
+            var existingPlaceholder = placeholderContainer.Children.LastOrDefault() as Placeholder;
+
+            if (placeholder != null && placeholder.Equals(existingPlaceholder))
                 return;
-            }
 
-            var existingPlaceholder = placeholderContainer.Children.FirstOrDefault() as Placeholder;
+            existingPlaceholder?.FadeOut(150, Easing.OutQuint).Expire();
 
-            if (placeholder.Equals(existingPlaceholder))
+            if (placeholder == null)
                 return;
 
             Scores = null;
 
-            placeholderContainer.Clear(true);
-            placeholderContainer.Child = placeholder;
-            placeholderContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
+            placeholderContainer.Add(placeholder);
+
+            placeholder.ScaleTo(0.8f).Then().ScaleTo(1, fade_duration * 3, Easing.OutQuint);
+            placeholder.FadeInFromZero(fade_duration, Easing.OutQuint);
         }
 
         protected override void Update()
@@ -322,6 +319,12 @@ namespace osu.Game.Screens.Select.Leaderboards
 
         private abstract class Placeholder : FillFlowContainer, IEquatable<Placeholder>
         {
+            protected Placeholder()
+            {
+                Anchor = Anchor.Centre;
+                Origin = Anchor.Centre;
+            }
+
             public virtual bool Equals(Placeholder other) => GetType() == other?.GetType();
         }
 
@@ -429,6 +432,7 @@ namespace osu.Game.Screens.Select.Leaderboards
         Successful,
         Retrieving,
         NetworkFailure,
+        Unavailable,
         NoScores,
         NotLoggedIn,
         NotSupporter,

From bdda1570d1540148ee31f16107897fbb3359ef11 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 22 Dec 2017 22:44:18 +0900
Subject: [PATCH 2/3] Move Placeholder classes to own files

---
 .../Select/Leaderboards/Leaderboard.cs        | 106 ------------------
 .../Select/Leaderboards/MessagePlaceholder.cs |  38 +++++++
 .../Select/Leaderboards/Placeholder.cs        |  20 ++++
 .../RetrievalFailurePlaceholder.cs            |  77 +++++++++++++
 osu.Game/osu.Game.csproj                      |   3 +
 5 files changed, 138 insertions(+), 106 deletions(-)
 create mode 100644 osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs
 create mode 100644 osu.Game/Screens/Select/Leaderboards/Placeholder.cs
 create mode 100644 osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs

diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
index 4b5eefe5b6..c15a179e8c 100644
--- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
@@ -19,11 +19,8 @@ using osu.Game.Online.API;
 using osu.Game.Online.API.Requests;
 using System.Linq;
 using osu.Framework.Configuration;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics;
 using osu.Framework.Logging;
 using osu.Game.Rulesets;
-using osu.Framework.Input;
 
 namespace osu.Game.Screens.Select.Leaderboards
 {
@@ -157,9 +154,7 @@ namespace osu.Game.Screens.Select.Leaderboards
         }
 
         private APIAccess api;
-
         private BeatmapInfo beatmap;
-
         private OsuGame osuGame;
 
         private ScheduledDelegate pendingBeatmapSwitch;
@@ -316,107 +311,6 @@ namespace osu.Game.Screens.Select.Leaderboards
                 }
             }
         }
-
-        private abstract class Placeholder : FillFlowContainer, IEquatable<Placeholder>
-        {
-            protected Placeholder()
-            {
-                Anchor = Anchor.Centre;
-                Origin = Anchor.Centre;
-            }
-
-            public virtual bool Equals(Placeholder other) => GetType() == other?.GetType();
-        }
-
-        private class MessagePlaceholder : Placeholder
-        {
-            private readonly string message;
-
-            public MessagePlaceholder(string message)
-            {
-                Direction = FillDirection.Horizontal;
-                AutoSizeAxes = Axes.Both;
-                Children = new Drawable[]
-                {
-                    new SpriteIcon
-                    {
-                        Icon = FontAwesome.fa_exclamation_circle,
-                        Size = new Vector2(26),
-                        Margin = new MarginPadding { Right = 10 },
-                    },
-                    new OsuSpriteText
-                    {
-                        Text = this.message = message,
-                        TextSize = 22,
-                    },
-                };
-            }
-
-            public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message;
-        }
-
-        private class RetrievalFailurePlaceholder : Placeholder
-        {
-            public Action OnRetry;
-
-            public RetrievalFailurePlaceholder()
-            {
-                Direction = FillDirection.Horizontal;
-                AutoSizeAxes = Axes.Both;
-                Children = new Drawable[]
-                {
-                    new RetryButton
-                    {
-                        Action = () => OnRetry?.Invoke(),
-                        Margin = new MarginPadding { Right = 10 },
-                    },
-                    new OsuSpriteText
-                    {
-                        Anchor = Anchor.TopLeft,
-                        Text = @"Couldn't retrieve scores!",
-                        TextSize = 22,
-                    },
-                };
-            }
-
-            private class RetryButton : OsuHoverContainer
-            {
-                private readonly SpriteIcon icon;
-
-                public Action Action;
-
-                public RetryButton()
-                {
-                    Height = 26;
-                    Width = 26;
-                    Child = new OsuClickableContainer
-                    {
-                        AutoSizeAxes = Axes.Both,
-                        Anchor = Anchor.Centre,
-                        Origin = Anchor.Centre,
-                        Action = () => Action?.Invoke(),
-                        Child = icon = new SpriteIcon
-                        {
-                            Icon = FontAwesome.fa_refresh,
-                            Size = new Vector2(26),
-                            Shadow = true,
-                        },
-                    };
-                }
-
-                protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
-                {
-                    icon.ScaleTo(0.8f, 4000, Easing.OutQuint);
-                    return base.OnMouseDown(state, args);
-                }
-
-                protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
-                {
-                    icon.ScaleTo(1, 1000, Easing.OutElastic);
-                    return base.OnMouseUp(state, args);
-                }
-            }
-        }
     }
 
     public enum LeaderboardScope
diff --git a/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs b/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs
new file mode 100644
index 0000000000..4f94087d30
--- /dev/null
+++ b/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs
@@ -0,0 +1,38 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using OpenTK;
+
+namespace osu.Game.Screens.Select.Leaderboards
+{
+    public class MessagePlaceholder : Placeholder
+    {
+        private readonly string message;
+
+        public MessagePlaceholder(string message)
+        {
+            Direction = FillDirection.Horizontal;
+            AutoSizeAxes = Axes.Both;
+            Children = new Drawable[]
+            {
+                new SpriteIcon
+                {
+                    Icon = FontAwesome.fa_exclamation_circle,
+                    Size = new Vector2(26),
+                    Margin = new MarginPadding { Right = 10 },
+                },
+                new OsuSpriteText
+                {
+                    Text = this.message = message,
+                    TextSize = 22,
+                },
+            };
+        }
+
+        public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message;
+    }
+}
diff --git a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs b/osu.Game/Screens/Select/Leaderboards/Placeholder.cs
new file mode 100644
index 0000000000..8427259106
--- /dev/null
+++ b/osu.Game/Screens/Select/Leaderboards/Placeholder.cs
@@ -0,0 +1,20 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Game.Screens.Select.Leaderboards
+{
+    public abstract class Placeholder : FillFlowContainer, IEquatable<Placeholder>
+    {
+        protected Placeholder()
+        {
+            Anchor = Anchor.Centre;
+            Origin = Anchor.Centre;
+        }
+
+        public virtual bool Equals(Placeholder other) => GetType() == other?.GetType();
+    }
+}
diff --git a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs
new file mode 100644
index 0000000000..7563c08c8b
--- /dev/null
+++ b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs
@@ -0,0 +1,77 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using OpenTK;
+
+namespace osu.Game.Screens.Select.Leaderboards
+{
+    public class RetrievalFailurePlaceholder : Placeholder
+    {
+        public Action OnRetry;
+
+        public RetrievalFailurePlaceholder()
+        {
+            Direction = FillDirection.Horizontal;
+            AutoSizeAxes = Axes.Both;
+            Children = new Drawable[]
+            {
+                new RetryButton
+                {
+                    Action = () => OnRetry?.Invoke(),
+                    Margin = new MarginPadding { Right = 10 },
+                },
+                new OsuSpriteText
+                {
+                    Anchor = Anchor.TopLeft,
+                    Text = @"Couldn't retrieve scores!",
+                    TextSize = 22,
+                },
+            };
+        }
+
+        public class RetryButton : OsuHoverContainer
+        {
+            private readonly SpriteIcon icon;
+
+            public Action Action;
+
+            public RetryButton()
+            {
+                Height = 26;
+                Width = 26;
+                Child = new OsuClickableContainer
+                {
+                    AutoSizeAxes = Axes.Both,
+                    Anchor = Anchor.Centre,
+                    Origin = Anchor.Centre,
+                    Action = () => Action?.Invoke(),
+                    Child = icon = new SpriteIcon
+                    {
+                        Icon = FontAwesome.fa_refresh,
+                        Size = new Vector2(26),
+                        Shadow = true,
+                    },
+                };
+            }
+
+            protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+            {
+                icon.ScaleTo(0.8f, 4000, Easing.OutQuint);
+                return base.OnMouseDown(state, args);
+            }
+
+            protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+            {
+                icon.ScaleTo(1, 1000, Easing.OutElastic);
+                return base.OnMouseUp(state, args);
+            }
+        }
+    }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1004d9dc95..1c78ba379e 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -781,6 +781,9 @@
     <Compile Include="Screens\Select\Leaderboards\DrawableRank.cs" />
     <Compile Include="Screens\Select\Leaderboards\Leaderboard.cs" />
     <Compile Include="Screens\Select\Leaderboards\LeaderboardScore.cs" />
+    <Compile Include="Screens\Select\Leaderboards\MessagePlaceholder.cs" />
+    <Compile Include="Screens\Select\Leaderboards\Placeholder.cs" />
+    <Compile Include="Screens\Select\Leaderboards\RetrievalFailurePlaceholder.cs" />
     <Compile Include="Screens\Select\MatchSongSelect.cs" />
     <Compile Include="Screens\Select\Options\BeatmapOptionsButton.cs" />
     <Compile Include="Screens\Select\Options\BeatmapOptionsOverlay.cs" />

From 1f44e98f9cc823760897cf15d83f937890df21a1 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Sat, 23 Dec 2017 20:17:12 +0900
Subject: [PATCH 3/3] Add unavailable state to testcase

---
 osu.Game.Tests/Visual/TestCaseLeaderboard.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
index 30a442594c..a01dbb7a6b 100644
--- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
+++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
@@ -36,6 +36,7 @@ namespace osu.Game.Tests.Visual
             AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
             AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
             AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
+            AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable));
             AddStep(@"Real beatmap", realBeatmap);
         }