From d89f9590ea5d86bde2a07ebedacb107671c860e0 Mon Sep 17 00:00:00 2001
From: Naeferith <toto2020@hotmail.fr>
Date: Sat, 4 Mar 2017 23:48:28 +0100
Subject: [PATCH 001/179] Visual test for lobby room panels

---
 .../Tests/TestCaseMultiRoomPanel.cs           | 71 +++++++++++++++++++
 1 file changed, 71 insertions(+)
 create mode 100644 osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
new file mode 100644
index 0000000000..0ef0e39b1b
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
@@ -0,0 +1,71 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Screens.Testing;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Screens.Select;
+using osu.Game.Screens.Multiplayer;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+    class TestCaseMultiRoomPanel : TestCase
+    {
+        private MultiRoomPanel test;
+        private FlowContainer panelContainer;
+        public override string Name => @"MultiRoomPanel";
+        public override string Description => @"Select your favourite room";
+
+        private void action(int action)
+        {
+            switch (action)
+            {
+                case 0:
+                    if (test.Status == 0) test.Status = 1;
+                    else test.Status = 0;
+                    break;
+            }
+        }
+
+        public override void Reset()
+        {
+            base.Reset();
+
+            AddButton(@"ChangeState", () => action(0));
+
+            Add(panelContainer = new FlowContainer //Positionning container
+            {
+                RelativeSizeAxes = Axes.Both,
+                Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
+                Direction = FlowDirections.Vertical,
+                Size = new Vector2(0.4f, 0.5f),
+                Children = new Drawable[]
+                {
+                    test = new MultiRoomPanel("Great Room Right Here", "Naeferith", 0),
+                    new MultiRoomPanel("Relax it's the weekend", "Someone", 1),
+                }
+            });
+        }
+        protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+        {
+            foreach (MultiRoomPanel panel in panelContainer.Children)
+            {
+                panel.BorderThickness = 0;
+                if (panel.Clicked == true)
+                {
+                    panel.BorderThickness = 3;
+                    panel.Clicked = false;
+                }
+            }
+            return base.OnMouseUp(state, args);
+        }
+    }
+}

From 89a443fb9261ccc63e788f5646127e910e627a7a Mon Sep 17 00:00:00 2001
From: Naeferith <toto2020@hotmail.fr>
Date: Sat, 4 Mar 2017 23:49:47 +0100
Subject: [PATCH 002/179] Lobby's Room Panel

---
 .../Screens/Multiplayer/MultiRoomPanel.cs     | 261 ++++++++++++++++++
 1 file changed, 261 insertions(+)
 create mode 100644 osu.Game/Screens/Multiplayer/MultiRoomPanel.cs

diff --git a/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs b/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
new file mode 100644
index 0000000000..386b9217e3
--- /dev/null
+++ b/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
@@ -0,0 +1,261 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Screens.Multiplayer
+{
+    public class MultiRoomPanel : ClickableContainer
+    {
+        private bool didClick;
+        private string roomName;
+        private string hostName;
+
+        private int roomStatus;
+        private Color4 statusColour;
+        private string statusString;
+
+        private Box sideSprite;
+        private OsuSpriteText hostSprite;
+        private OsuSpriteText statusSprite;
+        private OsuSpriteText roomSprite;
+
+        public const int BORDER_SIZE = 3;
+        public const int PANEL_HEIGHT = 90;
+        
+
+        public Color4 ColourFree = new Color4(166, 204, 0, 255);
+        public Color4 ColourBusy = new Color4(135, 102, 237, 255);
+
+        public int CONTENT_PADDING = 5;
+
+        public bool Clicked
+        {
+            get { return didClick; }
+            set
+            {
+                didClick = value;
+            }
+        }
+
+        public int Status
+        {
+            get { return roomStatus; }
+            set
+            {
+                roomStatus = value;
+                if (roomStatus == 0)
+                {
+                    statusColour = ColourFree;
+                    statusString = "Welcoming Players";
+
+                    UpdatePanel(this);
+                }
+                else
+                {
+                    statusColour = ColourBusy;
+                    statusString = "Now Playing";
+
+                    UpdatePanel(this);
+                }
+            }
+        }
+
+        public void UpdatePanel(MultiRoomPanel panel)
+        {
+            panel.BorderColour = statusColour;
+            panel.sideSprite.Colour = statusColour;
+
+            statusSprite.Colour = statusColour;
+            statusSprite.Text = statusString;
+        }
+
+        public MultiRoomPanel(string matchName = "Room Name", string host = "Undefined", int status = 0)
+        {
+            roomName = matchName;
+            hostName = host;
+            roomStatus = status;
+
+            if (status == 0)
+            {
+                statusColour = ColourFree;
+                statusString = "Welcoming Players";
+            }
+            else
+            {
+                statusColour = ColourBusy;
+                statusString = "Now Playing";
+            }
+
+            RelativeSizeAxes = Axes.X;
+            Height = PANEL_HEIGHT;
+            Masking = true;
+            CornerRadius = 5;
+            BorderThickness = 0;
+            BorderColour = statusColour;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Colour = Color4.Black.Opacity(40),
+                Radius = 5,
+            };
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = new Color4(34,34,34, 255),
+                },
+                sideSprite = new Box
+                {
+                    RelativeSizeAxes = Axes.Y,
+                    Width = 5,
+                    Colour = statusColour,
+                },
+                /*new Box //Beatmap img 
+                {
+
+                },*/
+                new Background(@"Backgrounds/bg4")
+                {
+                    RelativeSizeAxes = Axes.Both,
+                }
+                ,
+                new FlowContainer
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Anchor = Anchor.TopRight,
+                    Origin = Anchor.TopRight,
+                    Direction = FlowDirections.Vertical,
+                    Size = new Vector2(0.75f,1),
+
+                    Children = new Drawable[]
+                    {
+                        roomSprite = new OsuSpriteText
+                        {
+                            Text = roomName,
+                            TextSize = 18,
+                            Margin = new MarginPadding { Top = CONTENT_PADDING },
+                        },
+                        new FlowContainer
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            Height = 20,
+                            Direction = FlowDirections.Horizontal,
+                            Spacing = new Vector2(5,0),
+                            Children = new Drawable[]
+                            {
+                                
+                                
+                                new Container
+                                {
+                                    Masking = true,
+                                    CornerRadius = 5,
+                                    Width = 30,
+                                    Height = 20,
+                                    Children = new Drawable[]
+                                    {
+                                        new Box
+                                        {
+                                            RelativeSizeAxes = Axes.Both,
+                                            Colour = Color4.Gray,
+                                        }
+                                    }
+                                    
+                                },
+                                new Container
+                                {
+                                    Masking = true,
+                                    CornerRadius = 5,
+                                    Width = 40,
+                                    Height = 20,
+                                    Children = new Drawable[]
+                                    {
+                                        new Box
+                                        {
+                                            RelativeSizeAxes = Axes.Both,
+                                            Colour = new Color4(173,56,126,255),
+                                        }
+                                    }
+                                },
+                                new OsuSpriteText
+                                {
+                                    Text = "hosted by",
+                                    Anchor = Anchor.CentreLeft,
+                                    Origin = Anchor.CentreLeft,
+                                    TextSize = 14,
+                                },
+                                hostSprite = new OsuSpriteText
+                                {
+                                    Text = hostName,
+                                    Font = @"Exo2.0-Bold",
+                                    Anchor = Anchor.CentreLeft,
+                                    Origin = Anchor.CentreLeft,
+                                    Shear = new Vector2(0.1f,0),
+                                    Colour = new Color4(69,179,222,255),
+                                    TextSize = 14,
+                                },
+                                new OsuSpriteText
+                                {
+                                    Text = "#6895 - #50024",
+                                    Anchor = Anchor.CentreLeft,
+                                    Origin = Anchor.CentreLeft,
+                                    TextSize = 14,
+                                    Margin = new MarginPadding { Left = 80 },
+                                    Colour = new Color4(153,153,153,255),
+                                }
+                            }
+                        },
+                        statusSprite = new OsuSpriteText
+                        {
+                            Text = statusString,
+                            TextSize = 14,
+                            Font = @"Exo2.0-Bold",
+                            Colour = statusColour,
+                            Margin = new MarginPadding { Top = 10 }
+                        },
+                        new FlowContainer
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            Direction = FlowDirections.Horizontal,
+                            Children = new Drawable[]
+                            {
+                                new OsuSpriteText
+                                {
+                                    Text = "Platina",
+                                    Font = @"Exo2.0-Bold",
+                                    TextSize = 14,
+                                    Shear = new Vector2(0.1f,0),
+                                    Colour = new Color4(153,153,153,255),
+                                },
+                                new OsuSpriteText
+                                {
+                                    Text = " - " + "Maaya Sakamoto",
+                                    TextSize = 14,
+                                    Shear = new Vector2(0.1f,0),
+                                    Colour = new Color4(153,153,153,255),
+                                }
+                            }
+                        },
+                    }
+                },
+            };
+        }
+
+        protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+        {
+            BorderThickness = 3;
+            didClick = true;
+            return base.OnMouseUp(state, args);
+        }
+    }
+}
\ No newline at end of file

From 2a269dbc5afa9a630eedaed10c573901526ef56b Mon Sep 17 00:00:00 2001
From: Naeferith <toto2020@hotmail.fr>
Date: Fri, 28 Apr 2017 10:57:59 +0200
Subject: [PATCH 003/179] Remove unsused variable + IStateful Implementation

---
 .../Screens/Multiplayer/MultiRoomPanel.cs     | 68 +++++++++++--------
 1 file changed, 38 insertions(+), 30 deletions(-)

diff --git a/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs b/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
index 386b9217e3..174ec19f75 100644
--- a/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
+++ b/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
@@ -1,8 +1,9 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK;
 using OpenTK.Graphics;
+using osu.Framework;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Primitives;
@@ -14,8 +15,35 @@ using osu.Game.Graphics.Sprites;
 
 namespace osu.Game.Screens.Multiplayer
 {
-    public class MultiRoomPanel : ClickableContainer
+    public class MultiRoomPanel : ClickableContainer, IStateful<MultiRoomPanel.PanelState>
     {
+        private PanelState state;
+        public PanelState State
+        {
+            get { return state; }
+            set
+            {
+                if (state == value)
+                    return;
+
+                state = value;
+                switch (state)
+                {
+                    case PanelState.Free:
+                        statusColour = ColourFree;
+                        statusString = "Welcoming Players";
+                        UpdatePanel(this);
+                        break;
+
+                    case PanelState.Busy:
+                        statusColour = ColourBusy;
+                        statusString = "Now Playing";
+                        UpdatePanel(this);
+                        break;
+                }
+            }
+        }
+
         private bool didClick;
         private string roomName;
         private string hostName;
@@ -29,15 +57,12 @@ namespace osu.Game.Screens.Multiplayer
         private OsuSpriteText statusSprite;
         private OsuSpriteText roomSprite;
 
-        public const int BORDER_SIZE = 3;
         public const int PANEL_HEIGHT = 90;
-        
+        public const int CONTENT_PADDING = 5;
 
         public Color4 ColourFree = new Color4(166, 204, 0, 255);
         public Color4 ColourBusy = new Color4(135, 102, 237, 255);
 
-        public int CONTENT_PADDING = 5;
-
         public bool Clicked
         {
             get { return didClick; }
@@ -47,29 +72,6 @@ namespace osu.Game.Screens.Multiplayer
             }
         }
 
-        public int Status
-        {
-            get { return roomStatus; }
-            set
-            {
-                roomStatus = value;
-                if (roomStatus == 0)
-                {
-                    statusColour = ColourFree;
-                    statusString = "Welcoming Players";
-
-                    UpdatePanel(this);
-                }
-                else
-                {
-                    statusColour = ColourBusy;
-                    statusString = "Now Playing";
-
-                    UpdatePanel(this);
-                }
-            }
-        }
-
         public void UpdatePanel(MultiRoomPanel panel)
         {
             panel.BorderColour = statusColour;
@@ -257,5 +259,11 @@ namespace osu.Game.Screens.Multiplayer
             didClick = true;
             return base.OnMouseUp(state, args);
         }
+
+        public enum PanelState
+        {
+            Free,
+            Busy
+        }
     }
-}
\ No newline at end of file
+}

From bf6c1705411178b545effba584820eecaa0666bd Mon Sep 17 00:00:00 2001
From: Naeferith <toto2020@hotmail.fr>
Date: Fri, 28 Apr 2017 10:59:34 +0200
Subject: [PATCH 004/179] IStateful implementation

---
 osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
index 0ef0e39b1b..29cc4481a9 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK;
@@ -28,8 +28,7 @@ namespace osu.Desktop.VisualTests.Tests
             switch (action)
             {
                 case 0:
-                    if (test.Status == 0) test.Status = 1;
-                    else test.Status = 0;
+                    test.State = test.State == MultiRoomPanel.PanelState.Free ? MultiRoomPanel.PanelState.Busy : MultiRoomPanel.PanelState.Free;
                     break;
             }
         }

From 9ba356f2c65754785dc151f48e22726117fcc82b Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 17 May 2017 05:58:34 -0300
Subject: [PATCH 005/179] Added osu!direct header and filter control

---
 .../Tests/TestCaseDirect.cs                   |  27 +++
 .../osu.Desktop.VisualTests.csproj            |   1 +
 .../Graphics/UserInterface/OsuDropdown.cs     |   2 +-
 .../Graphics/UserInterface/OsuEnumDropdown.cs |  32 +++
 .../Graphics/UserInterface/OsuTabControl.cs   |  12 +-
 osu.Game/OsuGame.cs                           |   8 +
 osu.Game/Overlays/Direct/FilterControl.cs     | 188 ++++++++++++++++++
 osu.Game/Overlays/Direct/Header.cs            | 123 ++++++++++++
 osu.Game/Overlays/Direct/SortTabControl.cs    | 117 +++++++++++
 osu.Game/Overlays/DirectOverlay.cs            |  89 +++++++++
 osu.Game/Screens/Menu/MainMenu.cs             |   1 +
 osu.Game/osu.Game.csproj                      |   8 +
 12 files changed, 601 insertions(+), 7 deletions(-)
 create mode 100644 osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
 create mode 100644 osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
 create mode 100644 osu.Game/Overlays/Direct/FilterControl.cs
 create mode 100644 osu.Game/Overlays/Direct/Header.cs
 create mode 100644 osu.Game/Overlays/Direct/SortTabControl.cs
 create mode 100644 osu.Game/Overlays/DirectOverlay.cs

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
new file mode 100644
index 0000000000..8e2e88dd00
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -0,0 +1,27 @@
+// 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.Testing;
+using osu.Game.Graphics;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Dialog;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+    public class TestCaseDirect : TestCase
+    {
+        public override string Description => @"osu!direct overlay";
+
+        private DirectOverlay direct;
+
+        public override void Reset()
+        {
+            base.Reset();
+
+            Add(direct = new DirectOverlay());
+
+            AddStep(@"Toggle", direct.ToggleVisibility);
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
index dbb1750b72..8ec0fc83db 100644
--- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
+++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
@@ -220,6 +220,7 @@
     <Compile Include="Tests\TestCaseLeaderboard.cs" />
     <Compile Include="Beatmaps\TestWorkingBeatmap.cs" />
     <Compile Include="Tests\TestCaseBeatmapDetailArea.cs" />
+    <Compile Include="Tests\TestCaseDirect.cs" />
   </ItemGroup>
   <ItemGroup />
   <ItemGroup />
diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index 4f286ba7b5..9c1799c04c 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -113,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface
             }
         }
 
-        protected class OsuDropdownHeader : DropdownHeader
+        public class OsuDropdownHeader : DropdownHeader
         {
             private readonly SpriteText label;
             protected override string Label
diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
new file mode 100644
index 0000000000..fe828ca65b
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
@@ -0,0 +1,32 @@
+// 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 System.ComponentModel;
+using System.Reflection;
+using System.Collections.Generic;
+
+namespace osu.Game.Graphics.UserInterface
+{
+    public class OsuEnumDropdown<T> : OsuDropdown<T>
+    {
+        public OsuEnumDropdown()
+        {
+            if (!typeof(T).IsEnum)
+                throw new InvalidOperationException("SettingsDropdown only supports enums as the generic type argument");
+
+            List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
+            foreach(var val in (T[])Enum.GetValues(typeof(T)))
+            {
+                var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
+                items.Add(
+                    new KeyValuePair<string, T>(
+                        field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
+                        val
+                    )
+                );
+            }
+            Items = items;
+        }
+    }
+}
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index a9fad0c739..4bbae4efd1 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -57,9 +57,9 @@ namespace osu.Game.Graphics.UserInterface
             }
         }
 
-        private class OsuTabItem : TabItem<T>
+        public class OsuTabItem : TabItem<T>
         {
-            private readonly SpriteText text;
+            protected readonly SpriteText Text;
             private readonly Box box;
 
             private Color4? accentColour;
@@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface
                 {
                     accentColour = value;
                     if (!Active)
-                        text.Colour = value;
+                        Text.Colour = value;
                 }
             }
 
@@ -94,13 +94,13 @@ namespace osu.Game.Graphics.UserInterface
             private void fadeActive()
             {
                 box.FadeIn(transition_length, EasingTypes.OutQuint);
-                text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint);
+                Text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint);
             }
 
             private void fadeInactive()
             {
                 box.FadeOut(transition_length, EasingTypes.OutQuint);
-                text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint);
+                Text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint);
             }
 
             protected override bool OnHover(InputState state)
@@ -130,7 +130,7 @@ namespace osu.Game.Graphics.UserInterface
 
                 Children = new Drawable[]
                 {
-                    text = new OsuSpriteText
+                    Text = new OsuSpriteText
                     {
                         Margin = new MarginPadding { Top = 5, Bottom = 5 },
                         Origin = Anchor.BottomLeft,
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 4cf436a8e4..5bbf28e2ef 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -41,6 +41,8 @@ namespace osu.Game
 
         private DialogOverlay dialogOverlay;
 
+        private DirectOverlay direct;
+
         private Intro intro
         {
             get
@@ -70,6 +72,8 @@ namespace osu.Game
 
         public void ToggleSettings() => settings.ToggleVisibility();
 
+        public void ToggleDirect() => direct.ToggleVisibility();
+
         [BackgroundDependencyLoader]
         private void load()
         {
@@ -160,6 +164,7 @@ namespace osu.Game
             });
 
             //overlay elements
+            LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, overlayContent.Add);
             LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add);
             LoadComponentAsync(settings = new SettingsOverlay { Depth = -1 }, overlayContent.Add);
             LoadComponentAsync(musicController = new MusicController
@@ -249,6 +254,9 @@ namespace osu.Game
                     case Key.O:
                         settings.ToggleVisibility();
                         return true;
+                    case Key.D:
+                        direct.ToggleVisibility();
+                        return true;
                 }
             }
 
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
new file mode 100644
index 0000000000..9990681024
--- /dev/null
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -0,0 +1,188 @@
+// 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.ComponentModel;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+
+using Container = osu.Framework.Graphics.Containers.Container;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class FilterControl : Container
+    {
+        private readonly Box tabStrip;
+        private readonly FillFlowContainer<ModeToggleButton> modeButtons;
+        private readonly OsuDropdown<RankStatus> rankStatusDropdown;
+
+        public readonly SearchTextBox Search;
+
+        public enum RankStatus
+        {
+            Any,
+            [Description("Ranked & Approved")]
+            RankedApproved,
+            Approved,
+            Loved,
+            Favourites,
+            [Description("Mod Requests")]
+            ModRequests,
+            Pending,
+            Graveyard,
+            [Description("My Maps")]
+            MyMaps,
+        }
+
+        protected override bool InternalContains(Vector2 screenSpacePos) => true;
+
+        public FilterControl()
+        {
+            RelativeSizeAxes = Axes.X;
+            //AutoSizeAxes = Axes.Y;
+            Height = 127;
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"384552"),
+                },
+                tabStrip = new Box
+                {
+                    Anchor = Anchor.BottomLeft,
+                    Origin = Anchor.BottomLeft,
+                    Position = new Vector2(0f, 1f),
+                    RelativeSizeAxes = Axes.X,
+                    Height = 1,
+                },
+                new FillFlowContainer
+                {
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING, Top = 10 },
+                    Spacing = new Vector2(0f, 10f),
+                    Children = new Drawable[]
+                    {
+                        Search = new DirectSearchTextBox
+                        {
+                            RelativeSizeAxes = Axes.X,
+                        },
+                        modeButtons = new FillFlowContainer<ModeToggleButton>
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Spacing = new Vector2(10f, 0f),
+                        },
+                        new SortTabControl
+                        {
+                            RelativeSizeAxes = Axes.X,
+                        },
+                    },
+                },
+                rankStatusDropdown = new SlimEnumDropdown<RankStatus>
+                {
+                    Anchor = Anchor.TopRight,
+                    Origin = Anchor.TopRight,
+                    RelativeSizeAxes = Axes.None,
+                    Margin = new MarginPadding { Top = 93, Bottom = 5, Right = DirectOverlay.WIDTH_PADDING }, //todo: sort of hacky positioning
+                    Width = 160f,
+                },
+            };
+
+            rankStatusDropdown.Current.Value = RankStatus.RankedApproved;
+        }
+
+        [BackgroundDependencyLoader(true)]
+        private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
+        {
+            tabStrip.Colour = colours.Yellow;
+            rankStatusDropdown.AccentColour = colours.BlueDark;
+
+            foreach (var r in rulesets.AllRulesets)
+            {
+                modeButtons.Add(new ModeToggleButton(game?.Ruleset ?? new Bindable<RulesetInfo>(), r));
+            }
+        }
+
+        private class DirectSearchTextBox : SearchTextBox
+        {
+            protected override Color4 BackgroundUnfocused => OsuColour.FromHex(@"222222");
+            protected override Color4 BackgroundFocused => OsuColour.FromHex(@"222222");
+        }
+
+        private class ModeToggleButton : ClickableContainer
+        {
+            private TextAwesome icon;
+
+            private RulesetInfo ruleset;
+            public RulesetInfo Ruleset
+            {
+                get { return ruleset; }
+                set
+                {
+                    ruleset = value;
+                    icon.Icon = Ruleset.CreateInstance().Icon;
+                }
+            }
+
+            private Bindable<RulesetInfo> bindable;
+
+            void Bindable_ValueChanged(RulesetInfo obj)
+            {
+                icon.FadeTo((Ruleset == obj) ? 1f : 0.5f, 100);
+            }
+
+            public ModeToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)
+            {
+                this.bindable = bindable;
+                AutoSizeAxes = Axes.Both;
+
+                Children = new[]
+                {
+                    icon = new TextAwesome
+                    {
+                        Origin = Anchor.TopLeft,
+                        Anchor = Anchor.TopLeft,
+                        TextSize = 32,
+                    }
+                };
+
+                Ruleset = ruleset;
+                bindable.ValueChanged += Bindable_ValueChanged;
+                Bindable_ValueChanged(null);
+                Action = () => bindable.Value = Ruleset;
+            }
+
+            protected override void Dispose(bool isDisposing)
+            {
+                if (bindable != null)
+                    bindable.ValueChanged -= Bindable_ValueChanged;
+                base.Dispose(isDisposing);
+            }
+        }
+
+        private class SlimEnumDropdown<T> : OsuEnumDropdown<T>
+        {
+            protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
+
+            private class SlimDropdownHeader : OsuDropdownHeader
+            {
+                public SlimDropdownHeader()
+                {
+                    Height = 25;
+                    Icon.TextSize = 16;
+                    Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4, };
+                }
+            }
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs
new file mode 100644
index 0000000000..dfcb0c038f
--- /dev/null
+++ b/osu.Game/Overlays/Direct/Header.cs
@@ -0,0 +1,123 @@
+// 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 System.ComponentModel;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+
+using Container = osu.Framework.Graphics.Containers.Container;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class Header : Container
+    {
+        private readonly Box tabStrip;
+        private readonly DirectTabControl tabs;
+
+        public Action<DirectTab> OnSelectTab;
+
+        public Header()
+        {
+            Height = 90;
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"252f3a"),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.CentreLeft,
+                            Origin = Anchor.BottomLeft,
+                            Position = new Vector2(-35f, 5f),
+                            AutoSizeAxes = Axes.Both,
+                            Direction = FillDirection.Horizontal,
+                            Spacing = new Vector2(10f, 0f),
+                            Children = new Drawable[]
+                            {
+                                new TextAwesome
+                                {
+                                    TextSize = 25,
+                                    Icon = FontAwesome.fa_osu_chevron_down_o,
+                                },
+                                new OsuSpriteText
+                                {
+                                    TextSize = 25,
+                                    Text = @"osu!direct",
+                                },
+                            },
+                        },
+                        tabStrip = new Box
+                        {
+                            Anchor = Anchor.BottomLeft,
+                            Origin = Anchor.BottomLeft,
+                            Width = 282, //todo: make this actually match the tab control's width instead of hardcoding
+                            Height = 1,
+                        },
+                        tabs = new DirectTabControl
+                        {
+                            Anchor = Anchor.BottomLeft,
+                            Origin = Anchor.BottomLeft,
+                            RelativeSizeAxes = Axes.X,
+                        },
+                    },
+                },
+            };
+
+            tabs.Current.ValueChanged += (newValue) => OnSelectTab?.Invoke(newValue);
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            tabStrip.Colour = colours.Green;
+        }
+
+        private class DirectTabControl : OsuTabControl<DirectTab>
+        {
+            protected override TabItem<DirectTab> CreateTabItem(DirectTab value) => new DirectTabItem(value);
+
+            public DirectTabControl()
+            {
+                Height = 25;
+                AccentColour = Color4.White;
+            }
+
+            private class DirectTabItem : OsuTabControl<DirectTab>.OsuTabItem
+            {
+                public DirectTabItem(DirectTab value) : base(value)
+                {
+                    Text.TextSize = 15;
+                }
+            }
+        }
+    }
+
+    public enum DirectTab
+    {
+        Search,
+        [Description("Newest Maps")]
+        New,
+        [Description("Top Rated")]
+        Top,
+        [Description("Most Played")]
+        MostP
+    }
+}
diff --git a/osu.Game/Overlays/Direct/SortTabControl.cs b/osu.Game/Overlays/Direct/SortTabControl.cs
new file mode 100644
index 0000000000..5da41e5fc5
--- /dev/null
+++ b/osu.Game/Overlays/Direct/SortTabControl.cs
@@ -0,0 +1,117 @@
+// 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 OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class SortTabControl : OsuTabControl<SortCriteria>
+    {
+        protected override TabItem<SortCriteria> CreateTabItem(SortCriteria value) => new SortTabItem(value);
+
+        public SortTabControl()
+        {
+            Height = 30;
+        }
+
+        private class SortTabItem : TabItem<SortCriteria>
+        {
+            private readonly float transition_duration = 100;
+
+            private Box box;
+
+            public override bool Active
+            {
+                get { return base.Active; }
+                set
+                {
+                    if (Active == value) return;
+
+                    if (value)
+                        slideActive();
+                    else
+                        slideInactive();
+                    base.Active = value;
+                }
+            }
+
+            public SortTabItem(SortCriteria value) : base(value)
+            {
+                AutoSizeAxes = Axes.X;
+                RelativeSizeAxes = Axes.Y;
+
+                Children = new Drawable[]
+                {
+                new OsuSpriteText
+                {
+                    Margin = new MarginPadding { Top = 8, Bottom = 8 },
+                    Origin = Anchor.BottomLeft,
+                    Anchor = Anchor.BottomLeft,
+                    Text = (value as Enum)?.GetDescription() ?? value.ToString(),
+                    TextSize = 14,
+                    Font = @"Exo2.0-Bold",
+                },
+                box = new Box
+                {
+                    RelativeSizeAxes = Axes.X,
+                    Height = 5,
+                    Scale = new Vector2(1f, 0f),
+                    Colour = Color4.White,
+                    Origin = Anchor.BottomLeft,
+                    Anchor = Anchor.BottomLeft,
+                },
+                };
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                box.Colour = colours.Yellow;
+            }
+
+            protected override bool OnHover(InputState state)
+            {
+                if (!Active)
+                    slideActive();
+                return true;
+            }
+
+            protected override void OnHoverLost(InputState state)
+            {
+                if (!Active)
+                    slideInactive();
+            }
+
+            private void slideActive()
+            {
+                box.ScaleTo(new Vector2(1f), transition_duration);
+            }
+
+            private void slideInactive()
+            {
+                box.ScaleTo(new Vector2(1f, 0f), transition_duration);
+            }
+        }
+    }
+
+    public enum SortCriteria
+    {
+        Title,
+        Artist,
+        Creator,
+        Difficulty,
+        Ranked,
+        Rating,
+    }
+}
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
new file mode 100644
index 0000000000..09b45431d6
--- /dev/null
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -0,0 +1,89 @@
+// 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.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Overlays.Direct;
+
+namespace osu.Game.Overlays
+{
+    public class DirectOverlay : WaveOverlayContainer
+    {
+        public static readonly int WIDTH_PADDING = 80;
+
+        private readonly Box background;
+        private readonly FilterControl filter;
+
+        public DirectOverlay()
+        {
+            RelativeSizeAxes = Axes.Both;
+
+            // osu!direct colours are not part of the standard palette
+
+            FirstWaveColour = OsuColour.FromHex(@"19b0e2");
+            SecondWaveColour = OsuColour.FromHex(@"2280a2");
+            ThirdWaveColour = OsuColour.FromHex(@"005774");
+            FourthWaveColour = OsuColour.FromHex(@"003a4e");
+
+            Children = new Drawable[]
+            {
+                background = new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"485e74"),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Masking = true,
+                    Children = new[]
+                    {
+                        new Triangles
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            TriangleScale = 5,
+                            ColourLight = OsuColour.FromHex(@"465b71"),
+                            ColourDark = OsuColour.FromHex(@"3f5265"),
+                        },
+                    },
+                },
+                new FillFlowContainer
+                {
+                    AutoSizeAxes = Axes.Y,
+                    RelativeSizeAxes = Axes.X,
+                    Children = new Drawable[]
+                    {
+                        new Header
+                        {
+                            RelativeSizeAxes = Axes.X,
+                        },
+                        filter = new FilterControl
+                        {
+                            RelativeSizeAxes = Axes.X,
+                        },
+                    },
+                },
+            };
+
+            filter.Search.Exit = Hide;
+        }
+
+        protected override void PopIn()
+        {
+            base.PopIn();
+
+            filter.Search.HoldFocus = true;
+            Schedule(() => filter.Search.TriggerFocus());
+        }
+
+        protected override void PopOut()
+        {
+            base.PopOut();
+
+            filter.Search.HoldFocus = false;
+        }
+    }
+}
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 71d020b0f2..61e1ee0832 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -85,6 +85,7 @@ namespace osu.Game.Screens.Menu
             }
 
             buttons.OnSettings = game.ToggleSettings;
+            buttons.OnDirect = game.ToggleDirect;
 
             preloadSongSelect();
         }
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 63acdb1914..96a823c9e7 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -428,6 +428,11 @@
     <Compile Include="Overlays\Music\PlaylistOverlay.cs" />
     <Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
     <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
+    <Compile Include="Overlays\DirectOverlay.cs" />
+    <Compile Include="Overlays\Direct\FilterControl.cs" />
+    <Compile Include="Overlays\Direct\Header.cs" />
+    <Compile Include="Overlays\Direct\SortTabControl.cs" />
+    <Compile Include="Graphics\UserInterface\OsuEnumDropdown.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">
@@ -450,6 +455,9 @@
   <ItemGroup />
   <ItemGroup />
   <ItemGroup />
+  <ItemGroup>
+    <Folder Include="Overlays\Direct\" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
        Other similar extension points exist, see Microsoft.Common.targets.

From 5fb445e3fee8f09ba4117660828897cf7c26ebe1 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 17 May 2017 16:37:34 -0300
Subject: [PATCH 006/179] Basic direct panel, minor cleanups

---
 .../Graphics/UserInterface/OsuDropdown.cs     |   2 +-
 osu.Game/Overlays/Direct/DirectPanel.cs       | 240 ++++++++++++++++++
 osu.Game/Overlays/Direct/FilterControl.cs     |  21 +-
 osu.Game/Overlays/DirectOverlay.cs            |   8 +
 osu.Game/osu.Game.csproj                      |   1 +
 5 files changed, 264 insertions(+), 8 deletions(-)
 create mode 100644 osu.Game/Overlays/Direct/DirectPanel.cs

diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index 9c1799c04c..dbd50c2fe0 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface
             }
 
             public OsuDropdownHeader()
-            {
+            {   
                 Foreground.Padding = new MarginPadding(4);
 
                 AutoSizeAxes = Axes.None;
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
new file mode 100644
index 0000000000..5b5d054d5c
--- /dev/null
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -0,0 +1,240 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class DirectPanel : Container
+    {
+        private readonly float horizontal_padding = 10;
+        private readonly float vertical_padding = 5;
+
+        private readonly Sprite background;
+        private readonly OsuSpriteText title, artist, mapperPrefix, mapper, source;
+        private readonly Statistic playCount, favouriteCount;
+        private readonly FillFlowContainer difficultyIcons;
+
+        private DirectPanelStyle style;
+        public DirectPanelStyle Style
+        {
+            get { return style; }
+            set
+            {
+                if (value == style) return;
+                style = value;
+            }
+        }
+
+        public DirectPanel()
+        {
+            Height = 135 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
+            CornerRadius = 4;
+            Masking = true;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Offset = new Vector2(0f, 1f),
+                Radius = 3f,
+                Colour = Color4.Black.Opacity(0.25f),
+            };
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black,
+                },
+                background = new Sprite
+                {
+                    FillMode = FillMode.Fill,
+                },
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black.Opacity(0.5f),
+                },
+                new FillFlowContainer
+                {
+                    Anchor = Anchor.BottomLeft,
+                    Origin = Anchor.BottomLeft,
+                    Direction = FillDirection.Vertical,
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Spacing = new Vector2(0f, vertical_padding),
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding },
+                            Direction = FillDirection.Vertical,
+                            Children = new[]
+                            {
+                                title = new OsuSpriteText
+                                {
+                                    TextSize = 18,
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                                artist = new OsuSpriteText
+                                {
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                            },
+                        },
+                        new Container
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Children = new Drawable[]
+                            {
+                                new Box
+                                {
+                                    RelativeSizeAxes = Axes.Both,
+                                },
+                                new FillFlowContainer
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Direction = FillDirection.Vertical,
+                                    Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = horizontal_padding },
+                                    Children = new Drawable[]
+                                    {
+                                        new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.Both,
+                                            Direction = FillDirection.Horizontal,
+                                            Children = new[]
+                                            {
+                                                mapperPrefix = new OsuSpriteText
+                                                {
+                                                    Text = @"mapped by ",
+                                                    TextSize = 14,
+                                                    Shadow = false,
+                                                },
+                                                mapper = new OsuSpriteText
+                                                {
+                                                    TextSize = 14,
+                                                    Font = @"Exo2.0-SemiBoldItalic",
+                                                    Shadow = false,
+                                                },
+                                            },
+                                        },
+                                        source = new OsuSpriteText
+                                        {
+                                            TextSize = 14,
+                                            Shadow = false,
+                                        },
+                                        difficultyIcons = new FillFlowContainer
+                                        {
+                                            Margin = new MarginPadding { Top = vertical_padding },
+                                            AutoSizeAxes = Axes.Both,
+                                            Children = new[]
+                                            {
+                                                new Box //todo: placeholder
+                                                {
+                                                    Size = new Vector2(16f),
+                                                },
+                                            },
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+                new FillFlowContainer
+                {
+                    Anchor = Anchor.TopRight,
+                    Origin = Anchor.TopRight,
+                    AutoSizeAxes = Axes.Both,
+                    Direction = FillDirection.Vertical,
+                    Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
+                    Children = new[]
+                    {
+                        playCount = new Statistic(FontAwesome.fa_play_circle)
+                        {
+                            Margin = new MarginPadding { Right = 1 },
+                        },
+                        favouriteCount = new Statistic(FontAwesome.fa_heart),
+                    },
+                },
+            };
+
+            title.Text = @"Platina";
+            artist.Text = @"Maaya Sakamoto";
+            mapper.Text = @"TicClick";
+            source.Text = @"from Cardcaptor Sakura";
+            playCount.Value = 4579492;
+            favouriteCount.Value = 2659;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(TextureStore textures, OsuColour colours)
+        {
+            background.Texture = textures.Get(@"Backgrounds/bg4");
+
+            mapperPrefix.Colour = colours.Gray5;
+            mapper.Colour = colours.BlueDark;
+            source.Colour = colours.Gray5;
+        }
+
+        private class Statistic : FillFlowContainer
+        {
+            private readonly SpriteText text;
+
+            private int value;
+            public int Value
+            {
+                get { return value; }
+                set
+                {
+                    this.value = value;
+                    text.Text = string.Format("{0:n0}", Value);
+                }
+            }
+
+            public Statistic(FontAwesome icon, int value = 0)
+            {
+                Anchor = Anchor.TopRight;
+                Origin = Anchor.TopRight;
+                AutoSizeAxes = Axes.Both;
+                Direction = FillDirection.Horizontal;
+                Spacing = new Vector2(5f, 0f);
+
+                Children = new Drawable[]
+                {
+                    text = new OsuSpriteText
+                    {
+                        Font = @"Exo2.0-SemiBoldItalic",
+                    },
+                    new TextAwesome
+                    {
+                        Icon = icon,
+                        Shadow = true,
+                        TextSize = 14,
+                        Margin = new MarginPadding { Top = 1 },
+                    },
+                };
+
+                Value = value;
+            }
+        }
+    }
+
+    public enum DirectPanelStyle
+    {
+        Grid,
+        List,
+    }
+}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 9990681024..18870285e5 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -47,8 +47,7 @@ namespace osu.Game.Overlays.Direct
         public FilterControl()
         {
             RelativeSizeAxes = Axes.X;
-            //AutoSizeAxes = Axes.Y;
-            Height = 127;
+            AutoSizeAxes = Axes.Y;
 
             Children = new Drawable[]
             {
@@ -90,10 +89,10 @@ namespace osu.Game.Overlays.Direct
                 },
                 rankStatusDropdown = new SlimEnumDropdown<RankStatus>
                 {
-                    Anchor = Anchor.TopRight,
-                    Origin = Anchor.TopRight,
+                    Anchor = Anchor.BottomRight,
+                    Origin = Anchor.BottomRight,
                     RelativeSizeAxes = Axes.None,
-                    Margin = new MarginPadding { Top = 93, Bottom = 5, Right = DirectOverlay.WIDTH_PADDING }, //todo: sort of hacky positioning
+                    Margin = new MarginPadding { Bottom = 5, Right = DirectOverlay.WIDTH_PADDING },
                     Width = 160f,
                 },
             };
@@ -115,8 +114,16 @@ namespace osu.Game.Overlays.Direct
 
         private class DirectSearchTextBox : SearchTextBox
         {
-            protected override Color4 BackgroundUnfocused => OsuColour.FromHex(@"222222");
-            protected override Color4 BackgroundFocused => OsuColour.FromHex(@"222222");
+            protected override Color4 BackgroundUnfocused => backgroundColour;
+            protected override Color4 BackgroundFocused => backgroundColour;
+
+            private Color4 backgroundColour;
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                backgroundColour = colours.Gray2;
+            }
         }
 
         private class ModeToggleButton : ClickableContainer
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 09b45431d6..e1a6d236b8 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using OpenTK;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
@@ -66,6 +67,13 @@ namespace osu.Game.Overlays
                         },
                     },
                 },
+                new DirectPanel
+                {
+                    Anchor = Anchor.Centre,
+                    Origin = Anchor.Centre,
+                    Style = DirectPanelStyle.Grid,
+                    Width = 300,
+                }
             };
 
             filter.Search.Exit = Hide;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 96a823c9e7..df5a5e4034 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -433,6 +433,7 @@
     <Compile Include="Overlays\Direct\Header.cs" />
     <Compile Include="Overlays\Direct\SortTabControl.cs" />
     <Compile Include="Graphics\UserInterface\OsuEnumDropdown.cs" />
+    <Compile Include="Overlays\Direct\DirectPanel.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">

From cabfe72c92eab084791ac24ca9384f63512f371e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 17 May 2017 18:02:33 -0300
Subject: [PATCH 007/179] Changed DirectPanel to a base class for
 DirectGridPanel and DirectListPanel

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs | 180 ++++++++++++++++++
 osu.Game/Overlays/Direct/DirectListPanel.cs | 167 +++++++++++++++++
 osu.Game/Overlays/Direct/DirectPanel.cs     | 195 ++------------------
 osu.Game/Overlays/DirectOverlay.cs          |  13 +-
 osu.Game/osu.Game.csproj                    |   2 +
 5 files changed, 376 insertions(+), 181 deletions(-)
 create mode 100644 osu.Game/Overlays/Direct/DirectGridPanel.cs
 create mode 100644 osu.Game/Overlays/Direct/DirectListPanel.cs

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
new file mode 100644
index 0000000000..95405544a8
--- /dev/null
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -0,0 +1,180 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class DirectGridPanel : DirectPanel
+    {
+        private readonly float horizontal_padding = 10;
+        private readonly float vertical_padding = 5;
+
+        private readonly Sprite background;
+        private readonly OsuSpriteText title, artist, mapperPrefix, mapper, source;
+        private readonly Statistic playCount, favouriteCount;
+        private readonly FillFlowContainer difficultyIcons;
+
+        protected override Sprite Background => background;
+        protected override OsuSpriteText Title => title;
+        protected override OsuSpriteText Artist => artist;
+        protected override OsuSpriteText Mapper => mapper;
+        protected override OsuSpriteText Source => source;
+        protected override Statistic PlayCount => playCount;
+        protected override Statistic FavouriteCount => favouriteCount;
+        protected override FillFlowContainer DifficultyIcons => difficultyIcons;
+
+        public DirectGridPanel()
+        {
+            Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
+            CornerRadius = 4;
+            Masking = true;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Offset = new Vector2(0f, 1f),
+                Radius = 3f,
+                Colour = Color4.Black.Opacity(0.25f),
+            };
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black,
+                },
+                background = new Sprite
+                {
+                    FillMode = FillMode.Fill,
+                },
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black.Opacity(0.5f),
+                },
+                new FillFlowContainer
+                {
+                    Anchor = Anchor.BottomLeft,
+                    Origin = Anchor.BottomLeft,
+                    Direction = FillDirection.Vertical,
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Spacing = new Vector2(0f, vertical_padding),
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding },
+                            Direction = FillDirection.Vertical,
+                            Children = new[]
+                            {
+                                title = new OsuSpriteText
+                                {
+                                    TextSize = 18,
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                                artist = new OsuSpriteText
+                                {
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                            },
+                        },
+                        new Container
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Children = new Drawable[]
+                            {
+                                new Box
+                                {
+                                    RelativeSizeAxes = Axes.Both,
+                                },
+                                new FillFlowContainer
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Direction = FillDirection.Vertical,
+                                    Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = horizontal_padding },
+                                    Children = new Drawable[]
+                                    {
+                                        new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.Both,
+                                            Direction = FillDirection.Horizontal,
+                                            Children = new[]
+                                            {
+                                                mapperPrefix = new OsuSpriteText
+                                                {
+                                                    Text = @"mapped by ",
+                                                    TextSize = 14,
+                                                    Shadow = false,
+                                                },
+                                                mapper = new OsuSpriteText
+                                                {
+                                                    TextSize = 14,
+                                                    Font = @"Exo2.0-SemiBoldItalic",
+                                                    Shadow = false,
+                                                },
+                                            },
+                                        },
+                                        source = new OsuSpriteText
+                                        {
+                                            TextSize = 14,
+                                            Shadow = false,
+                                        },
+                                        difficultyIcons = new FillFlowContainer
+                                        {
+                                            Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding  },
+                                            AutoSizeAxes = Axes.Both,
+                                            Children = new[]
+                                            {
+                                                new Box //todo: placeholder
+                                                {
+                                                    Size = new Vector2(16f),
+                                                },
+                                            },
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+                new FillFlowContainer
+                {
+                    Anchor = Anchor.TopRight,
+                    Origin = Anchor.TopRight,
+                    AutoSizeAxes = Axes.Both,
+                    Direction = FillDirection.Vertical,
+                    Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
+                    Children = new[]
+                    {
+                        playCount = new Statistic(FontAwesome.fa_play_circle)
+                        {
+	                        Margin = new MarginPadding { Right = 1 },
+                        },
+                        favouriteCount = new Statistic(FontAwesome.fa_heart),
+                    },
+                },
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+        	mapperPrefix.Colour = colours.Gray5;
+        	Mapper.Colour = colours.BlueDark;
+        	Source.Colour = colours.Gray5;
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
new file mode 100644
index 0000000000..00ef4c3206
--- /dev/null
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -0,0 +1,167 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Colour;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class DirectListPanel : DirectPanel
+    {
+        private readonly float horizontal_padding = 10;
+        private readonly float vertical_padding = 5;
+        private readonly float height = 70;
+
+        private readonly Sprite background;
+        private readonly OsuSpriteText title, artist, mapper, source;
+        private readonly Statistic playCount, favouriteCount;
+        private readonly FillFlowContainer difficultyIcons;
+
+        protected override Sprite Background => background;
+        protected override OsuSpriteText Title => title;
+        protected override OsuSpriteText Artist => artist;
+        protected override OsuSpriteText Mapper => mapper;
+        protected override OsuSpriteText Source => source;
+        protected override Statistic PlayCount => playCount;
+        protected override Statistic FavouriteCount => favouriteCount;
+        protected override FillFlowContainer DifficultyIcons => difficultyIcons;
+
+        public DirectListPanel()
+        {
+            RelativeSizeAxes = Axes.X;
+            Height = height;
+            CornerRadius = 5;
+            Masking = true;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Offset = new Vector2(0f, 1f),
+                Radius = 3f,
+                Colour = Color4.Black.Opacity(0.25f),
+            };
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black,
+                },
+                background = new Sprite
+                {
+                    FillMode = FillMode.Fill,
+                },
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    ColourInfo = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.25f), Color4.Black.Opacity(0.75f)),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = vertical_padding },
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Direction = FillDirection.Vertical,
+                            Children = new Drawable[]
+                            {
+                                title = new OsuSpriteText
+                                {
+                                    TextSize = 18,
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                                artist = new OsuSpriteText
+                                {
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                                difficultyIcons = new FillFlowContainer
+                                {
+                                    Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
+                                    AutoSizeAxes = Axes.Both,
+                                },
+                            },
+                        },
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.TopRight,
+                            Origin = Anchor.TopRight,
+                            AutoSizeAxes = Axes.Both,
+                            Direction = FillDirection.Vertical,
+                            Margin = new MarginPadding { Right = (height - vertical_padding * 2) + vertical_padding },
+                            Children = new Drawable[]
+                            {
+                                playCount = new Statistic(FontAwesome.fa_play_circle, 4579492)
+                                {
+                                	Margin = new MarginPadding { Right = 1 },
+                                },
+                                favouriteCount = new Statistic(FontAwesome.fa_heart, 2659),
+                                new FillFlowContainer
+                                {
+                                    Anchor = Anchor.TopRight,
+                                    Origin = Anchor.TopRight,
+                                    AutoSizeAxes = Axes.Both,
+                                    Direction = FillDirection.Horizontal,
+                                    Children = new[]
+                                    {
+                                        new OsuSpriteText
+                                        {
+                                            Text = @"mapped by ",
+                                            TextSize = 14,
+                                        },
+                                        mapper = new OsuSpriteText
+                                        {
+                                            TextSize = 14,
+                                            Font = @"Exo2.0-SemiBoldItalic",
+                                        },
+                                    },
+                                },
+                                source = new OsuSpriteText
+                                {
+                                    Anchor = Anchor.TopRight,
+                                    Origin = Anchor.TopRight,
+                                    TextSize = 14,
+                                },
+                            },
+                        },
+                        new DownloadButton
+                        {
+                            Anchor = Anchor.TopRight,
+                            Origin = Anchor.TopRight,
+                            Size = new Vector2(height - (vertical_padding * 2)),
+                        },
+                    },
+                },
+            };
+        }
+
+        private class DownloadButton : ClickableContainer
+        {
+            //todo: proper download button animations
+            public DownloadButton()
+            {
+                Children = new Drawable[]
+                {
+                    new TextAwesome
+                    {
+                        Anchor = Anchor.Centre,
+                        Origin = Anchor.Centre,
+                        UseFullGlyphHeight = false,
+                        TextSize = 30,
+                        Icon = FontAwesome.fa_osu_chevron_down_o,
+                    },
+                };
+            }
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 5b5d054d5c..a5aa7b92fa 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -2,9 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK;
-using OpenTK.Graphics;
 using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
@@ -14,182 +12,31 @@ using osu.Game.Graphics.Sprites;
 
 namespace osu.Game.Overlays.Direct
 {
-    public class DirectPanel : Container
+    public abstract class DirectPanel : Container
     {
-        private readonly float horizontal_padding = 10;
-        private readonly float vertical_padding = 5;
-
-        private readonly Sprite background;
-        private readonly OsuSpriteText title, artist, mapperPrefix, mapper, source;
-        private readonly Statistic playCount, favouriteCount;
-        private readonly FillFlowContainer difficultyIcons;
-
-        private DirectPanelStyle style;
-        public DirectPanelStyle Style
-        {
-            get { return style; }
-            set
-            {
-                if (value == style) return;
-                style = value;
-            }
-        }
-
-        public DirectPanel()
-        {
-            Height = 135 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
-            CornerRadius = 4;
-            Masking = true;
-            EdgeEffect = new EdgeEffect
-            {
-                Type = EdgeEffectType.Shadow,
-                Offset = new Vector2(0f, 1f),
-                Radius = 3f,
-                Colour = Color4.Black.Opacity(0.25f),
-            };
-
-            Children = new Drawable[]
-            {
-                new Box
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Colour = Color4.Black,
-                },
-                background = new Sprite
-                {
-                    FillMode = FillMode.Fill,
-                },
-                new Box
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Colour = Color4.Black.Opacity(0.5f),
-                },
-                new FillFlowContainer
-                {
-                    Anchor = Anchor.BottomLeft,
-                    Origin = Anchor.BottomLeft,
-                    Direction = FillDirection.Vertical,
-                    RelativeSizeAxes = Axes.X,
-                    AutoSizeAxes = Axes.Y,
-                    Spacing = new Vector2(0f, vertical_padding),
-                    Children = new Drawable[]
-                    {
-                        new FillFlowContainer
-                        {
-                            AutoSizeAxes = Axes.Both,
-                            Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding },
-                            Direction = FillDirection.Vertical,
-                            Children = new[]
-                            {
-                                title = new OsuSpriteText
-                                {
-                                    TextSize = 18,
-                                    Font = @"Exo2.0-BoldItalic",
-                                },
-                                artist = new OsuSpriteText
-                                {
-                                    Font = @"Exo2.0-BoldItalic",
-                                },
-                            },
-                        },
-                        new Container
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            AutoSizeAxes = Axes.Y,
-                            Children = new Drawable[]
-                            {
-                                new Box
-                                {
-                                    RelativeSizeAxes = Axes.Both,
-                                },
-                                new FillFlowContainer
-                                {
-                                    RelativeSizeAxes = Axes.X,
-                                    AutoSizeAxes = Axes.Y,
-                                    Direction = FillDirection.Vertical,
-                                    Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = horizontal_padding },
-                                    Children = new Drawable[]
-                                    {
-                                        new FillFlowContainer
-                                        {
-                                            AutoSizeAxes = Axes.Both,
-                                            Direction = FillDirection.Horizontal,
-                                            Children = new[]
-                                            {
-                                                mapperPrefix = new OsuSpriteText
-                                                {
-                                                    Text = @"mapped by ",
-                                                    TextSize = 14,
-                                                    Shadow = false,
-                                                },
-                                                mapper = new OsuSpriteText
-                                                {
-                                                    TextSize = 14,
-                                                    Font = @"Exo2.0-SemiBoldItalic",
-                                                    Shadow = false,
-                                                },
-                                            },
-                                        },
-                                        source = new OsuSpriteText
-                                        {
-                                            TextSize = 14,
-                                            Shadow = false,
-                                        },
-                                        difficultyIcons = new FillFlowContainer
-                                        {
-                                            Margin = new MarginPadding { Top = vertical_padding },
-                                            AutoSizeAxes = Axes.Both,
-                                            Children = new[]
-                                            {
-                                                new Box //todo: placeholder
-                                                {
-                                                    Size = new Vector2(16f),
-                                                },
-                                            },
-                                        },
-                                    },
-                                },
-                            },
-                        },
-                    },
-                },
-                new FillFlowContainer
-                {
-                    Anchor = Anchor.TopRight,
-                    Origin = Anchor.TopRight,
-                    AutoSizeAxes = Axes.Both,
-                    Direction = FillDirection.Vertical,
-                    Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
-                    Children = new[]
-                    {
-                        playCount = new Statistic(FontAwesome.fa_play_circle)
-                        {
-                            Margin = new MarginPadding { Right = 1 },
-                        },
-                        favouriteCount = new Statistic(FontAwesome.fa_heart),
-                    },
-                },
-            };
-
-            title.Text = @"Platina";
-            artist.Text = @"Maaya Sakamoto";
-            mapper.Text = @"TicClick";
-            source.Text = @"from Cardcaptor Sakura";
-            playCount.Value = 4579492;
-            favouriteCount.Value = 2659;
-        }
+        protected virtual Sprite Background { get; }
+        protected virtual OsuSpriteText Title { get; }
+        protected virtual OsuSpriteText Artist { get; }
+        protected virtual OsuSpriteText Mapper { get; }
+        protected virtual OsuSpriteText Source { get; }
+        protected virtual Statistic PlayCount { get; }
+        protected virtual Statistic FavouriteCount { get; }
+        protected virtual FillFlowContainer DifficultyIcons { get; }
 
         [BackgroundDependencyLoader]
-        private void load(TextureStore textures, OsuColour colours)
+        private void load(TextureStore textures)
         {
-            background.Texture = textures.Get(@"Backgrounds/bg4");
+            Background.Texture = textures.Get(@"Backgrounds/bg4");
 
-            mapperPrefix.Colour = colours.Gray5;
-            mapper.Colour = colours.BlueDark;
-            source.Colour = colours.Gray5;
+            Title.Text = @"Platina";
+            Artist.Text = @"Maaya Sakamoto";
+            Mapper.Text = @"TicClick";
+            Source.Text = @"from Cardcaptor Sakura";
+            PlayCount.Value = 4579492;
+            FavouriteCount.Value = 2659;
         }
 
-        private class Statistic : FillFlowContainer
+        public class Statistic : FillFlowContainer
         {
             private readonly SpriteText text;
 
@@ -231,10 +78,4 @@ namespace osu.Game.Overlays.Direct
             }
         }
     }
-
-    public enum DirectPanelStyle
-    {
-        Grid,
-        List,
-    }
 }
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index e1a6d236b8..b7d27cf9e0 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -67,13 +67,18 @@ namespace osu.Game.Overlays
                         },
                     },
                 },
-                new DirectPanel
+                new DirectGridPanel
                 {
                     Anchor = Anchor.Centre,
-                    Origin = Anchor.Centre,
-                    Style = DirectPanelStyle.Grid,
+                    Origin = Anchor.BottomCentre,
                     Width = 300,
-                }
+                },
+                new DirectListPanel
+                {
+                    Anchor = Anchor.Centre,
+                    Origin = Anchor.TopCentre,
+                    Width = 0.8f,
+                },
             };
 
             filter.Search.Exit = Hide;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index df5a5e4034..e69a836829 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -434,6 +434,8 @@
     <Compile Include="Overlays\Direct\SortTabControl.cs" />
     <Compile Include="Graphics\UserInterface\OsuEnumDropdown.cs" />
     <Compile Include="Overlays\Direct\DirectPanel.cs" />
+    <Compile Include="Overlays\Direct\DirectGridPanel.cs" />
+    <Compile Include="Overlays\Direct\DirectListPanel.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">

From b3ef8b8fd47ede2992e01087839f845f417759c2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 17 May 2017 18:10:29 -0300
Subject: [PATCH 008/179] Design updates

---
 osu.Game/Overlays/Direct/FilterControl.cs | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 18870285e5..7a2adbad09 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -6,6 +6,7 @@ using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
@@ -55,6 +56,7 @@ namespace osu.Game.Overlays.Direct
                 {
                     RelativeSizeAxes = Axes.Both,
                     Colour = OsuColour.FromHex(@"384552"),
+                    Alpha = 0.9f,
                 },
                 tabStrip = new Box
                 {
@@ -122,7 +124,7 @@ namespace osu.Game.Overlays.Direct
             [BackgroundDependencyLoader]
             private void load(OsuColour colours)
             {
-                backgroundColour = colours.Gray2;
+                backgroundColour = colours.Gray2.Opacity(0.9f);
             }
         }
 
@@ -180,6 +182,7 @@ namespace osu.Game.Overlays.Direct
         private class SlimEnumDropdown<T> : OsuEnumDropdown<T>
         {
             protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
+            protected override Menu CreateMenu() => new SlimMenu();
 
             private class SlimDropdownHeader : OsuDropdownHeader
             {
@@ -187,7 +190,21 @@ namespace osu.Game.Overlays.Direct
                 {
                     Height = 25;
                     Icon.TextSize = 16;
-                    Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4, };
+                    Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
+                }
+
+                protected override void LoadComplete()
+                {
+                    base.LoadComplete();
+                    BackgroundColour = Color4.Black.Opacity(0.25f);
+                }
+            }
+
+            private class SlimMenu : OsuMenu
+            {
+                public SlimMenu()
+                {
+                    Background.Colour = Color4.Black.Opacity(0.25f);
                 }
             }
         }

From b26c8dd1c73bf23ae774440c67f4e2411bf2fe92 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 17 May 2017 18:42:40 -0300
Subject: [PATCH 009/179] Added temporary difficulties on the panels

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs | 7 -------
 osu.Game/Overlays/Direct/DirectPanel.cs     | 9 ++++++++-
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 95405544a8..ec0b625fe4 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -136,13 +136,6 @@ namespace osu.Game.Overlays.Direct
                                         {
                                             Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding  },
                                             AutoSizeAxes = Axes.Both,
-                                            Children = new[]
-                                            {
-                                                new Box //todo: placeholder
-                                                {
-                                                    Size = new Vector2(16f),
-                                                },
-                                            },
                                         },
                                     },
                                 },
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index a5aa7b92fa..8cb7f4f299 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -7,6 +7,8 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps.Drawables;
+using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
 
@@ -24,7 +26,7 @@ namespace osu.Game.Overlays.Direct
         protected virtual FillFlowContainer DifficultyIcons { get; }
 
         [BackgroundDependencyLoader]
-        private void load(TextureStore textures)
+        private void load(TextureStore textures, RulesetDatabase rulesets)
         {
             Background.Texture = textures.Get(@"Backgrounds/bg4");
 
@@ -34,6 +36,11 @@ namespace osu.Game.Overlays.Direct
             Source.Text = @"from Cardcaptor Sakura";
             PlayCount.Value = 4579492;
             FavouriteCount.Value = 2659;
+
+            for (int i = 0; i < 4; i++)
+            {
+                DifficultyIcons.Add(new DifficultyIcon(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i), StarDifficulty = i + 1 }));
+            }
         }
 
         public class Statistic : FillFlowContainer

From 9f36a39c59ea56870786d7c40ab1a9481842d646 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 13:57:19 -0300
Subject: [PATCH 010/179] More cleanup

---
 osu.Game/Overlays/Direct/FilterControl.cs | 49 ++++++++++++-----------
 osu.Game/Overlays/Direct/Header.cs        | 15 +++----
 2 files changed, 34 insertions(+), 30 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 7a2adbad09..78c9a37edf 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -23,27 +23,10 @@ namespace osu.Game.Overlays.Direct
     {
         private readonly Box tabStrip;
         private readonly FillFlowContainer<ModeToggleButton> modeButtons;
-        private readonly OsuDropdown<RankStatus> rankStatusDropdown;
 
         public readonly SearchTextBox Search;
-
-        public enum RankStatus
-        {
-            Any,
-            [Description("Ranked & Approved")]
-            RankedApproved,
-            Approved,
-            Loved,
-            Favourites,
-            [Description("Mod Requests")]
-            ModRequests,
-            Pending,
-            Graveyard,
-            [Description("My Maps")]
-            MyMaps,
-        }
-
-        protected override bool InternalContains(Vector2 screenSpacePos) => true;
+        public readonly SortTabControl SortTabs;
+        public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
 
         public FilterControl()
         {
@@ -83,13 +66,13 @@ namespace osu.Game.Overlays.Direct
                             AutoSizeAxes = Axes.Both,
                             Spacing = new Vector2(10f, 0f),
                         },
-                        new SortTabControl
+                        SortTabs = new SortTabControl
                         {
                             RelativeSizeAxes = Axes.X,
                         },
                     },
                 },
-                rankStatusDropdown = new SlimEnumDropdown<RankStatus>
+                RankStatusDropdown = new SlimEnumDropdown<RankStatus>
                 {
                     Anchor = Anchor.BottomRight,
                     Origin = Anchor.BottomRight,
@@ -99,14 +82,18 @@ namespace osu.Game.Overlays.Direct
                 },
             };
 
-            rankStatusDropdown.Current.Value = RankStatus.RankedApproved;
+            RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
+
+            //todo: possibly restore from config instead of always title
+            SortTabs.Current.Value = SortCriteria.Title;
+            SortTabs.Current.TriggerChange();
         }
 
         [BackgroundDependencyLoader(true)]
         private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
         {
             tabStrip.Colour = colours.Yellow;
-            rankStatusDropdown.AccentColour = colours.BlueDark;
+            RankStatusDropdown.AccentColour = colours.BlueDark;
 
             foreach (var r in rulesets.AllRulesets)
             {
@@ -209,4 +196,20 @@ namespace osu.Game.Overlays.Direct
             }
         }
     }
+
+    public enum RankStatus
+    {
+    	Any,
+    	[Description("Ranked & Approved")]
+    	RankedApproved,
+    	Approved,
+    	Loved,
+    	Favourites,
+    	[Description("Mod Requests")]
+    	ModRequests,
+    	Pending,
+    	Graveyard,
+    	[Description("My Maps")]
+    	MyMaps,
+    }
 }
diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs
index dfcb0c038f..0cd118a8ba 100644
--- a/osu.Game/Overlays/Direct/Header.cs
+++ b/osu.Game/Overlays/Direct/Header.cs
@@ -21,9 +21,8 @@ namespace osu.Game.Overlays.Direct
     public class Header : Container
     {
         private readonly Box tabStrip;
-        private readonly DirectTabControl tabs;
 
-        public Action<DirectTab> OnSelectTab;
+        public readonly OsuTabControl<DirectTab> Tabs;
 
         public Header()
         {
@@ -71,7 +70,7 @@ namespace osu.Game.Overlays.Direct
                             Width = 282, //todo: make this actually match the tab control's width instead of hardcoding
                             Height = 1,
                         },
-                        tabs = new DirectTabControl
+                        Tabs = new DirectTabControl
                         {
                             Anchor = Anchor.BottomLeft,
                             Origin = Anchor.BottomLeft,
@@ -81,7 +80,9 @@ namespace osu.Game.Overlays.Direct
                 },
             };
 
-            tabs.Current.ValueChanged += (newValue) => OnSelectTab?.Invoke(newValue);
+            //todo: possibly restore from config instead of always search
+            Tabs.Current.Value = DirectTab.Search;
+            Tabs.Current.TriggerChange();
         }
 
         [BackgroundDependencyLoader]
@@ -114,10 +115,10 @@ namespace osu.Game.Overlays.Direct
     {
         Search,
         [Description("Newest Maps")]
-        New,
+        NewestMaps,
         [Description("Top Rated")]
-        Top,
+        TopRated,
         [Description("Most Played")]
-        MostP
+        MostPlayed
     }
 }

From ab1401054def81a03a247f7f2509c071f18b1c63 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 14:08:14 -0300
Subject: [PATCH 011/179] Added result counts (visually)

---
 osu.Game/Overlays/Direct/FilterControl.cs | 56 ++++++++++++++++-------
 osu.Game/Overlays/Direct/Header.cs        |  2 +-
 2 files changed, 41 insertions(+), 17 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 78c9a37edf..d4b643c272 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Sprites;
 using osu.Framework.Graphics.UserInterface;
 using osu.Game.Database;
 using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
 using osu.Game.Graphics.UserInterface;
 
 using Container = osu.Framework.Graphics.Containers.Container;
@@ -23,6 +24,7 @@ namespace osu.Game.Overlays.Direct
     {
         private readonly Box tabStrip;
         private readonly FillFlowContainer<ModeToggleButton> modeButtons;
+        private FillFlowContainer resultCounts;
 
         public readonly SearchTextBox Search;
         public readonly SortTabControl SortTabs;
@@ -53,8 +55,8 @@ namespace osu.Game.Overlays.Direct
                 {
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y,
-                    Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING, Top = 10 },
                     Spacing = new Vector2(0f, 10f),
+                    Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING, Top = 10 },
                     Children = new Drawable[]
                     {
                         Search = new DirectSearchTextBox
@@ -80,11 +82,32 @@ namespace osu.Game.Overlays.Direct
                     Margin = new MarginPadding { Bottom = 5, Right = DirectOverlay.WIDTH_PADDING },
                     Width = 160f,
                 },
+                resultCounts = new FillFlowContainer
+                {
+                    Anchor = Anchor.BottomLeft,
+                    Origin = Anchor.TopLeft,
+                    AutoSizeAxes = Axes.Both,
+                    Direction = FillDirection.Horizontal,
+                    Margin = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Top = 6 },
+                    Children = new Drawable[]
+                    {
+                        new OsuSpriteText
+                        {
+                            Text = @"Found ",
+                            TextSize = 15,
+                        },
+                        new OsuSpriteText
+                        {
+                            Text = @"1 Artist, 432 Songs, 3 Tags",
+                            TextSize = 15,
+                            Font = @"Exo2.0-Bold",
+                        },
+                    }    
+                },
             };
 
-            RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
-
             //todo: possibly restore from config instead of always title
+            RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
             SortTabs.Current.Value = SortCriteria.Title;
             SortTabs.Current.TriggerChange();
         }
@@ -93,6 +116,7 @@ namespace osu.Game.Overlays.Direct
         private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
         {
             tabStrip.Colour = colours.Yellow;
+            resultCounts.Colour = colours.Yellow;
             RankStatusDropdown.AccentColour = colours.BlueDark;
 
             foreach (var r in rulesets.AllRulesets)
@@ -197,19 +221,19 @@ namespace osu.Game.Overlays.Direct
         }
     }
 
-    public enum RankStatus
-    {
-    	Any,
-    	[Description("Ranked & Approved")]
-    	RankedApproved,
-    	Approved,
-    	Loved,
-    	Favourites,
-    	[Description("Mod Requests")]
-    	ModRequests,
-    	Pending,
-    	Graveyard,
-    	[Description("My Maps")]
+    public enum RankStatus
+    {
+    	Any,
+    	[Description("Ranked & Approved")]
+    	RankedApproved,
+    	Approved,
+    	Loved,
+    	Favourites,
+    	[Description("Mod Requests")]
+    	ModRequests,
+    	Pending,
+    	Graveyard,
+    	[Description("My Maps")]
     	MyMaps,
     }
 }
diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs
index 0cd118a8ba..3e3bfe98d8 100644
--- a/osu.Game/Overlays/Direct/Header.cs
+++ b/osu.Game/Overlays/Direct/Header.cs
@@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Direct
                 },
             };
 
-            //todo: possibly restore from config instead of always search
+            //todo: possibly restore from config instead
             Tabs.Current.Value = DirectTab.Search;
             Tabs.Current.TriggerChange();
         }

From c3fb1ab7c60ad0fe5449ff4b4a0b484d1459158a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 15:01:01 -0300
Subject: [PATCH 012/179] Mapper -> Author, use BeatmapSetInfo for metadata

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs | 15 +++----
 osu.Game/Overlays/Direct/DirectListPanel.cs | 17 ++++----
 osu.Game/Overlays/Direct/DirectPanel.cs     | 35 ++++++++--------
 osu.Game/Overlays/DirectOverlay.cs          | 44 +++++++++++++++++----
 4 files changed, 72 insertions(+), 39 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index ec0b625fe4..e4faf7f783 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
+using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
 
@@ -19,20 +20,20 @@ namespace osu.Game.Overlays.Direct
         private readonly float vertical_padding = 5;
 
         private readonly Sprite background;
-        private readonly OsuSpriteText title, artist, mapperPrefix, mapper, source;
+        private readonly OsuSpriteText title, artist, authorPrefix, author, source;
         private readonly Statistic playCount, favouriteCount;
         private readonly FillFlowContainer difficultyIcons;
 
         protected override Sprite Background => background;
         protected override OsuSpriteText Title => title;
         protected override OsuSpriteText Artist => artist;
-        protected override OsuSpriteText Mapper => mapper;
+        protected override OsuSpriteText Author => author;
         protected override OsuSpriteText Source => source;
         protected override Statistic PlayCount => playCount;
         protected override Statistic FavouriteCount => favouriteCount;
         protected override FillFlowContainer DifficultyIcons => difficultyIcons;
 
-        public DirectGridPanel()
+        public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
         {
             Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
             CornerRadius = 4;
@@ -113,13 +114,13 @@ namespace osu.Game.Overlays.Direct
                                             Direction = FillDirection.Horizontal,
                                             Children = new[]
                                             {
-                                                mapperPrefix = new OsuSpriteText
+                                                authorPrefix = new OsuSpriteText
                                                 {
                                                     Text = @"mapped by ",
                                                     TextSize = 14,
                                                     Shadow = false,
                                                 },
-                                                mapper = new OsuSpriteText
+                                                author = new OsuSpriteText
                                                 {
                                                     TextSize = 14,
                                                     Font = @"Exo2.0-SemiBoldItalic",
@@ -165,8 +166,8 @@ namespace osu.Game.Overlays.Direct
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-        	mapperPrefix.Colour = colours.Gray5;
-        	Mapper.Colour = colours.BlueDark;
+        	authorPrefix.Colour = colours.Gray5;
+        	Author.Colour = colours.BlueDark;
         	Source.Colour = colours.Gray5;
         }
     }
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 00ef4c3206..b5146dbbe3 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -11,30 +11,31 @@ using osu.Framework.Graphics.Sprites;
 using osu.Framework.Graphics.Colour;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
+using osu.Game.Database;
 
 namespace osu.Game.Overlays.Direct
 {
     public class DirectListPanel : DirectPanel
     {
-        private readonly float horizontal_padding = 10;
+        private readonly float horizontal_padding = 10;
         private readonly float vertical_padding = 5;
         private readonly float height = 70;
 
         private readonly Sprite background;
-        private readonly OsuSpriteText title, artist, mapper, source;
+        private readonly OsuSpriteText title, artist, author, source;
         private readonly Statistic playCount, favouriteCount;
         private readonly FillFlowContainer difficultyIcons;
 
         protected override Sprite Background => background;
         protected override OsuSpriteText Title => title;
         protected override OsuSpriteText Artist => artist;
-        protected override OsuSpriteText Mapper => mapper;
+        protected override OsuSpriteText Author => author;
         protected override OsuSpriteText Source => source;
         protected override Statistic PlayCount => playCount;
         protected override Statistic FavouriteCount => favouriteCount;
         protected override FillFlowContainer DifficultyIcons => difficultyIcons;
 
-        public DirectListPanel()
+        public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap)
         {
             RelativeSizeAxes = Axes.X;
             Height = height;
@@ -101,11 +102,11 @@ namespace osu.Game.Overlays.Direct
                             Margin = new MarginPadding { Right = (height - vertical_padding * 2) + vertical_padding },
                             Children = new Drawable[]
                             {
-                                playCount = new Statistic(FontAwesome.fa_play_circle, 4579492)
-                                {
+                                playCount = new Statistic(FontAwesome.fa_play_circle)
+                                {
                                 	Margin = new MarginPadding { Right = 1 },
                                 },
-                                favouriteCount = new Statistic(FontAwesome.fa_heart, 2659),
+                                favouriteCount = new Statistic(FontAwesome.fa_heart),
                                 new FillFlowContainer
                                 {
                                     Anchor = Anchor.TopRight,
@@ -119,7 +120,7 @@ namespace osu.Game.Overlays.Direct
                                             Text = @"mapped by ",
                                             TextSize = 14,
                                         },
-                                        mapper = new OsuSpriteText
+                                        author = new OsuSpriteText
                                         {
                                             TextSize = 14,
                                             Font = @"Exo2.0-SemiBoldItalic",
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 8cb7f4f299..b163712e85 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -6,7 +6,7 @@ using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
+using osu.Framework.Localisation;
 using osu.Game.Beatmaps.Drawables;
 using osu.Game.Database;
 using osu.Game.Graphics;
@@ -19,28 +19,29 @@ namespace osu.Game.Overlays.Direct
         protected virtual Sprite Background { get; }
         protected virtual OsuSpriteText Title { get; }
         protected virtual OsuSpriteText Artist { get; }
-        protected virtual OsuSpriteText Mapper { get; }
+        protected virtual OsuSpriteText Author { get; }
         protected virtual OsuSpriteText Source { get; }
         protected virtual Statistic PlayCount { get; }
         protected virtual Statistic FavouriteCount { get; }
         protected virtual FillFlowContainer DifficultyIcons { get; }
 
-        [BackgroundDependencyLoader]
-        private void load(TextureStore textures, RulesetDatabase rulesets)
+        private BeatmapSetInfo setInfo;
+
+        [BackgroundDependencyLoader]
+        private void load(LocalisationEngine localisation)
+        {
+            Title.Current = localisation.GetUnicodePreference(setInfo.Metadata.TitleUnicode, setInfo.Metadata.Title);
+            Artist.Current = localisation.GetUnicodePreference(setInfo.Metadata.ArtistUnicode, setInfo.Metadata.Artist);
+            Author.Text = setInfo.Metadata.Author;
+            Source.Text = @"from " + setInfo.Metadata.Source;
+
+            foreach (var b in setInfo.Beatmaps)
+                DifficultyIcons.Add(new DifficultyIcon(b));
+        }
+
+        public DirectPanel(BeatmapSetInfo setInfo)
         {
-            Background.Texture = textures.Get(@"Backgrounds/bg4");
-
-            Title.Text = @"Platina";
-            Artist.Text = @"Maaya Sakamoto";
-            Mapper.Text = @"TicClick";
-            Source.Text = @"from Cardcaptor Sakura";
-            PlayCount.Value = 4579492;
-            FavouriteCount.Value = 2659;
-
-            for (int i = 0; i < 4; i++)
-            {
-                DifficultyIcons.Add(new DifficultyIcon(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i), StarDifficulty = i + 1 }));
-            }
+            this.setInfo = setInfo;
         }
 
         public class Statistic : FillFlowContainer
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index b7d27cf9e0..338ce2bd2d 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -1,10 +1,12 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using OpenTK;
+using System.Collections.Generic;
+using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
+using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
 using osu.Game.Overlays.Direct;
@@ -67,21 +69,49 @@ namespace osu.Game.Overlays
                         },
                     },
                 },
-                new DirectGridPanel
-                {
+            };
+
+            filter.Search.Exit = Hide;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(RulesetDatabase rulesets)
+        {
+            var setInfo = new BeatmapSetInfo
+            {
+                Metadata = new BeatmapMetadata
+                {
+                    Title = @"Platina",
+                    Artist = @"Maaya Sakamoto",
+                    Author = @"TicClick",
+                    Source = @"Cardcaptor Sakura",
+                },
+                Beatmaps = new List<BeatmapInfo>(),
+            };
+
+            for (int i = 0; i< 4; i++)
+            {
+                setInfo.Beatmaps.Add(new BeatmapInfo {
+                    Ruleset = rulesets.GetRuleset(i),
+                    StarDifficulty = i + 1,
+                });
+            }
+
+            Add(new Drawable[]
+            {
+                new DirectGridPanel(setInfo)
+                {
                     Anchor = Anchor.Centre,
                     Origin = Anchor.BottomCentre,
                     Width = 300,
                 },
-                new DirectListPanel
+                new DirectListPanel(setInfo)
                 {
                     Anchor = Anchor.Centre,
                     Origin = Anchor.TopCentre,
                     Width = 0.8f,
                 },
-            };
-
-            filter.Search.Exit = Hide;
+            });
         }
 
         protected override void PopIn()

From c2880676dbe9c877b5602cff14a4ecf753935bb2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 16:15:49 -0300
Subject: [PATCH 013/179] Added displaying sets

---
 .../Tests/TestCaseDirect.cs                   | 41 +++++++++
 osu.Game/Overlays/Direct/FilterControl.cs     |  5 ++
 osu.Game/Overlays/Direct/Header.cs            |  4 +-
 osu.Game/Overlays/DirectOverlay.cs            | 89 +++++++++----------
 4 files changed, 90 insertions(+), 49 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
index 8e2e88dd00..2fd8dc84b8 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -1,8 +1,11 @@
 // 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.Collections.Generic;
+using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Testing;
+using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Overlays;
 using osu.Game.Overlays.Dialog;
@@ -14,14 +17,52 @@ namespace osu.Desktop.VisualTests.Tests
         public override string Description => @"osu!direct overlay";
 
         private DirectOverlay direct;
+        private RulesetDatabase rulesets;
 
         public override void Reset()
         {
             base.Reset();
 
             Add(direct = new DirectOverlay());
+            newBeatmaps();
 
             AddStep(@"Toggle", direct.ToggleVisibility);
         }
+
+        [BackgroundDependencyLoader]
+        private void load(RulesetDatabase rulesets)
+        {
+            this.rulesets = rulesets;
+        }
+
+        private void newBeatmaps()
+        {
+            var setInfo = new BeatmapSetInfo
+            {
+                Metadata = new BeatmapMetadata
+                {
+                    Title = @"Platina",
+                    Artist = @"Maaya Sakamoto",
+                    Author = @"TicClick",
+                    Source = @"Cardcaptor Sakura",
+                },
+                Beatmaps = new List<BeatmapInfo>(),
+            };
+            
+            for (int i = 0; i < 4; i++)
+            {
+                setInfo.Beatmaps.Add(new BeatmapInfo
+                {
+                    Ruleset = rulesets.GetRuleset(i),
+                    StarDifficulty = i + 1,
+                });
+            }
+
+            var s = new List<BeatmapSetInfo>();
+            for (int i = 0; i < 10; i++)
+                s.Add(setInfo);
+
+            direct.BeatmapSets = s;
+        }
     }
 }
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index d4b643c272..0ea1953634 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -22,6 +22,11 @@ namespace osu.Game.Overlays.Direct
 {
     public class FilterControl : Container
     {
+        /// <summary>
+        /// The height of the content below the filter control (tab strip + result count text).
+        /// </summary>
+        public static readonly float LOWER_HEIGHT = 21;
+
         private readonly Box tabStrip;
         private readonly FillFlowContainer<ModeToggleButton> modeButtons;
         private FillFlowContainer resultCounts;
diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs
index 3e3bfe98d8..d4d8902b11 100644
--- a/osu.Game/Overlays/Direct/Header.cs
+++ b/osu.Game/Overlays/Direct/Header.cs
@@ -20,13 +20,15 @@ namespace osu.Game.Overlays.Direct
 {
     public class Header : Container
     {
+        public static readonly float HEIGHT = 90;
+
         private readonly Box tabStrip;
 
         public readonly OsuTabControl<DirectTab> Tabs;
 
         public Header()
         {
-            Height = 90;
+            Height = HEIGHT;
 
             Children = new Drawable[]
             {
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 338ce2bd2d..0b22506d5c 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
+using OpenTK;
 using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
@@ -16,9 +17,24 @@ namespace osu.Game.Overlays
     public class DirectOverlay : WaveOverlayContainer
     {
         public static readonly int WIDTH_PADDING = 80;
+        private readonly float panel_padding = 10f;
 
         private readonly Box background;
         private readonly FilterControl filter;
+        private readonly FillFlowContainer<DirectPanel> panels;
+
+        public IEnumerable<BeatmapSetInfo> BeatmapSets
+        {
+            set
+            {
+                var p = new List<DirectPanel>();
+
+                foreach (BeatmapSetInfo b in value)
+                    p.Add(new DirectGridPanel(b) { Width = 407 });
+
+                panels.Children = p;
+            }
+        }
 
         public DirectOverlay()
         {
@@ -53,67 +69,44 @@ namespace osu.Game.Overlays
                         },
                     },
                 },
-                new FillFlowContainer
+                new ScrollContainer
                 {
-                    AutoSizeAxes = Axes.Y,
-                    RelativeSizeAxes = Axes.X,
+                    RelativeSizeAxes = Axes.Both,
+                    ScrollDraggerVisible = false,
+                    Padding = new MarginPadding { Top = Header.HEIGHT },
                     Children = new Drawable[]
                     {
-                        new Header
-                        {
-                            RelativeSizeAxes = Axes.X,
-                        },
-                        filter = new FilterControl
+                        new FillFlowContainer
                         {
                             RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Direction = FillDirection.Vertical,
+                            Children = new Drawable[]
+                            {
+                                filter = new FilterControl
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                },
+                                panels = new FillFlowContainer<DirectPanel>
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Padding = new MarginPadding { Top = FilterControl.LOWER_HEIGHT + panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
+                                    Spacing = new Vector2(panel_padding),
+                                },
+                            },
                         },
                     },
                 },
+                new Header
+                {
+                    RelativeSizeAxes = Axes.X,
+                },
             };
 
             filter.Search.Exit = Hide;
         }
 
-        [BackgroundDependencyLoader]
-        private void load(RulesetDatabase rulesets)
-        {
-            var setInfo = new BeatmapSetInfo
-            {
-                Metadata = new BeatmapMetadata
-                {
-                    Title = @"Platina",
-                    Artist = @"Maaya Sakamoto",
-                    Author = @"TicClick",
-                    Source = @"Cardcaptor Sakura",
-                },
-                Beatmaps = new List<BeatmapInfo>(),
-            };
-
-            for (int i = 0; i< 4; i++)
-            {
-                setInfo.Beatmaps.Add(new BeatmapInfo {
-                    Ruleset = rulesets.GetRuleset(i),
-                    StarDifficulty = i + 1,
-                });
-            }
-
-            Add(new Drawable[]
-            {
-                new DirectGridPanel(setInfo)
-                {
-                    Anchor = Anchor.Centre,
-                    Origin = Anchor.BottomCentre,
-                    Width = 300,
-                },
-                new DirectListPanel(setInfo)
-                {
-                    Anchor = Anchor.Centre,
-                    Origin = Anchor.TopCentre,
-                    Width = 0.8f,
-                },
-            });
-        }
-
         protected override void PopIn()
         {
             base.PopIn();

From 0b480fe327f0ee04320dbe40d03344761067ed54 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 16:17:16 -0300
Subject: [PATCH 014/179] ModeToggleButton -> RulesetToggleButton

---
 osu.Game/Overlays/Direct/FilterControl.cs | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 0ea1953634..6b99dbb67d 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Direct
         public static readonly float LOWER_HEIGHT = 21;
 
         private readonly Box tabStrip;
-        private readonly FillFlowContainer<ModeToggleButton> modeButtons;
+        private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
         private FillFlowContainer resultCounts;
 
         public readonly SearchTextBox Search;
@@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Direct
                         {
                             RelativeSizeAxes = Axes.X,
                         },
-                        modeButtons = new FillFlowContainer<ModeToggleButton>
+                        modeButtons = new FillFlowContainer<RulesetToggleButton>
                         {
                             AutoSizeAxes = Axes.Both,
                             Spacing = new Vector2(10f, 0f),
@@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Direct
 
             foreach (var r in rulesets.AllRulesets)
             {
-                modeButtons.Add(new ModeToggleButton(game?.Ruleset ?? new Bindable<RulesetInfo>(), r));
+                modeButtons.Add(new RulesetToggleButton(game?.Ruleset ?? new Bindable<RulesetInfo>(), r));
             }
         }
 
@@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Direct
             }
         }
 
-        private class ModeToggleButton : ClickableContainer
+        private class RulesetToggleButton : ClickableContainer
         {
             private TextAwesome icon;
 
@@ -166,7 +166,7 @@ namespace osu.Game.Overlays.Direct
                 icon.FadeTo((Ruleset == obj) ? 1f : 0.5f, 100);
             }
 
-            public ModeToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)
+            public RulesetToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)
             {
                 this.bindable = bindable;
                 AutoSizeAxes = Axes.Both;

From 065f4faa70f1e961e39bef2d2f3b1f469973b881 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 16:34:34 -0300
Subject: [PATCH 015/179] Fix ruleset toggle buttons not updating when changing
 from the toolbar

---
 osu.Game/Overlays/Direct/FilterControl.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 6b99dbb67d..57c8d08766 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -163,7 +163,7 @@ namespace osu.Game.Overlays.Direct
 
             void Bindable_ValueChanged(RulesetInfo obj)
             {
-                icon.FadeTo((Ruleset == obj) ? 1f : 0.5f, 100);
+                icon.FadeTo((Ruleset.ID == obj.ID) ? 1f : 0.5f, 100);
             }
 
             public RulesetToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)
@@ -183,7 +183,7 @@ namespace osu.Game.Overlays.Direct
 
                 Ruleset = ruleset;
                 bindable.ValueChanged += Bindable_ValueChanged;
-                Bindable_ValueChanged(null);
+                Bindable_ValueChanged(bindable.Value);
                 Action = () => bindable.Value = Ruleset;
             }
 

From c8102db780eacd3ca8fa33862fba7a30674b338d Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 16:54:50 -0300
Subject: [PATCH 016/179] Fix visual test crash

---
 osu.Game/Overlays/Direct/FilterControl.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 57c8d08766..12bf68967e 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -163,7 +163,7 @@ namespace osu.Game.Overlays.Direct
 
             void Bindable_ValueChanged(RulesetInfo obj)
             {
-                icon.FadeTo((Ruleset.ID == obj.ID) ? 1f : 0.5f, 100);
+                icon.FadeTo((Ruleset.ID == obj?.ID) ? 1f : 0.5f, 100);
             }
 
             public RulesetToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)

From ca6826f3ba170b038a8696bf6c4ba2b9db59f977 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 17:12:43 -0300
Subject: [PATCH 017/179] Fix incorrect height between sort tabs and ruleset
 toggle buttons

---
 osu.Game/Overlays/Direct/FilterControl.cs | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 12bf68967e..3a8a0c1f58 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Direct
                 {
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y,
-                    Spacing = new Vector2(0f, 10f),
+                    //Spacing = new Vector2(0f, 10f),
                     Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING, Top = 10 },
                     Children = new Drawable[]
                     {
@@ -72,6 +72,7 @@ namespace osu.Game.Overlays.Direct
                         {
                             AutoSizeAxes = Axes.Both,
                             Spacing = new Vector2(10f, 0f),
+                            Margin = new MarginPadding { Top = 10f },
                         },
                         SortTabs = new SortTabControl
                         {
@@ -111,7 +112,7 @@ namespace osu.Game.Overlays.Direct
                 },
             };
 
-            //todo: possibly restore from config instead of always title
+            //todo: possibly restore from config
             RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
             SortTabs.Current.Value = SortCriteria.Title;
             SortTabs.Current.TriggerChange();

From 1d1375c4d4ef6bf15fca468a45423965c49dd9c8 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 17:13:53 -0300
Subject: [PATCH 018/179] Remove commented line

---
 osu.Game/Overlays/Direct/FilterControl.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 3a8a0c1f58..5a2d552209 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -60,7 +60,6 @@ namespace osu.Game.Overlays.Direct
                 {
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y,
-                    //Spacing = new Vector2(0f, 10f),
                     Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING, Top = 10 },
                     Children = new Drawable[]
                     {

From a5fa7e1a7d1cf407773cbbb21dc796beae065b6d Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 18 May 2017 17:43:39 -0300
Subject: [PATCH 019/179] Result counts displaying

---
 .../Tests/TestCaseDirect.cs                   |  6 ++-
 .../Graphics/UserInterface/OsuDropdown.cs     |  2 +-
 osu.Game/Overlays/Direct/DirectListPanel.cs   |  1 -
 osu.Game/Overlays/Direct/FilterControl.cs     | 48 +++++++++++++++++--
 osu.Game/Overlays/Direct/Header.cs            |  1 -
 osu.Game/Overlays/DirectOverlay.cs            |  9 +++-
 6 files changed, 55 insertions(+), 12 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
index 2fd8dc84b8..c7fa97952e 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -9,6 +9,7 @@ using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Overlays;
 using osu.Game.Overlays.Dialog;
+using osu.Game.Overlays.Direct;
 
 namespace osu.Desktop.VisualTests.Tests
 {
@@ -25,12 +26,13 @@ namespace osu.Desktop.VisualTests.Tests
 
             Add(direct = new DirectOverlay());
             newBeatmaps();
+            direct.ResultCounts = new ResultCounts(1, 432, 3);
 
             AddStep(@"Toggle", direct.ToggleVisibility);
         }
 
-        [BackgroundDependencyLoader]
-        private void load(RulesetDatabase rulesets)
+        [BackgroundDependencyLoader]
+        private void load(RulesetDatabase rulesets)
         {
             this.rulesets = rulesets;
         }
diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index dbd50c2fe0..9c1799c04c 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface
             }
 
             public OsuDropdownHeader()
-            {   
+            {
                 Foreground.Padding = new MarginPadding(4);
 
                 AutoSizeAxes = Axes.None;
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index b5146dbbe3..8de2121c0b 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -3,7 +3,6 @@
 
 using OpenTK;
 using OpenTK.Graphics;
-using osu.Framework.Allocation;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 5a2d552209..91372d7543 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -29,12 +29,20 @@ namespace osu.Game.Overlays.Direct
 
         private readonly Box tabStrip;
         private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
-        private FillFlowContainer resultCounts;
+        private FillFlowContainer resultCountsContainer;
+        private OsuSpriteText resultCountsText;
 
         public readonly SearchTextBox Search;
         public readonly SortTabControl SortTabs;
         public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
 
+        private ResultCounts resultCounts;
+        public  ResultCounts ResultCounts
+        {
+            get { return resultCounts; }
+            set { resultCounts = value; updateResultCounts(); }
+        }
+
         public FilterControl()
         {
             RelativeSizeAxes = Axes.X;
@@ -87,7 +95,7 @@ namespace osu.Game.Overlays.Direct
                     Margin = new MarginPadding { Bottom = 5, Right = DirectOverlay.WIDTH_PADDING },
                     Width = 160f,
                 },
-                resultCounts = new FillFlowContainer
+                resultCountsContainer = new FillFlowContainer
                 {
                     Anchor = Anchor.BottomLeft,
                     Origin = Anchor.TopLeft,
@@ -101,9 +109,8 @@ namespace osu.Game.Overlays.Direct
                             Text = @"Found ",
                             TextSize = 15,
                         },
-                        new OsuSpriteText
+                        resultCountsText = new OsuSpriteText
                         {
-                            Text = @"1 Artist, 432 Songs, 3 Tags",
                             TextSize = 15,
                             Font = @"Exo2.0-Bold",
                         },
@@ -115,13 +122,15 @@ namespace osu.Game.Overlays.Direct
             RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
             SortTabs.Current.Value = SortCriteria.Title;
             SortTabs.Current.TriggerChange();
+
+            updateResultCounts();
         }
 
         [BackgroundDependencyLoader(true)]
         private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
         {
             tabStrip.Colour = colours.Yellow;
-            resultCounts.Colour = colours.Yellow;
+            resultCountsContainer.Colour = colours.Yellow;
             RankStatusDropdown.AccentColour = colours.BlueDark;
 
             foreach (var r in rulesets.AllRulesets)
@@ -130,6 +139,21 @@ namespace osu.Game.Overlays.Direct
             }
         }
 
+        private void updateResultCounts()
+        {
+            resultCountsContainer.FadeTo(ResultCounts == null ? 0 : 1, 200, EasingTypes.Out);
+            if (resultCounts == null) return;
+
+            resultCountsText.Text = pluralize(@"Artist", ResultCounts.Artists) + ", " +
+                                    pluralize(@"Song", ResultCounts.Songs) + ", " +
+                                    pluralize(@"Tag", ResultCounts.Tags);
+        }
+
+        private string pluralize(string prefix, int value)
+        {
+            return $@"{value} {prefix}" + (value == 1 ? @"" : @"s");
+        }
+
         private class DirectSearchTextBox : SearchTextBox
         {
             protected override Color4 BackgroundUnfocused => backgroundColour;
@@ -241,4 +265,18 @@ namespace osu.Game.Overlays.Direct
     	[Description("My Maps")]
     	MyMaps,
     }
+
+    public class ResultCounts
+    {
+        public readonly int Artists;
+        public readonly int Songs;
+        public readonly int Tags;
+
+        public ResultCounts(int artists, int songs, int tags)
+        {
+            Artists = artists;
+            Songs = songs;
+            Tags = tags;
+        }
+    }
 }
diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs
index d4d8902b11..af1dfb4b53 100644
--- a/osu.Game/Overlays/Direct/Header.cs
+++ b/osu.Game/Overlays/Direct/Header.cs
@@ -1,7 +1,6 @@
 // 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 System.ComponentModel;
 using OpenTK;
 using OpenTK.Graphics;
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 0b22506d5c..7e167bd2c1 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -3,7 +3,6 @@
 
 using System.Collections.Generic;
 using OpenTK;
-using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
@@ -30,12 +29,18 @@ namespace osu.Game.Overlays
                 var p = new List<DirectPanel>();
 
                 foreach (BeatmapSetInfo b in value)
-                    p.Add(new DirectGridPanel(b) { Width = 407 });
+                    p.Add(new DirectGridPanel(b) { Width = 400 });
 
                 panels.Children = p;
             }
         }
 
+        public ResultCounts ResultCounts
+        {
+            get { return filter.ResultCounts; }
+            set { filter.ResultCounts = value; }
+        }
+
         public DirectOverlay()
         {
             RelativeSizeAxes = Axes.Both;

From 3b8cadd4dfd5b7417a9d41ebd529116fb4407b1b Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 12:52:23 -0300
Subject: [PATCH 020/179] Clean up DirectPanel and it's subclasses

---
 .../Drawables/BeatmapBackgroundSprite.cs      |  2 +-
 .../Drawables/DifficultyColouredContainer.cs  |  2 +-
 osu.Game/Beatmaps/Drawables/DifficultyIcon.cs |  2 +-
 osu.Game/Overlays/Direct/DirectGridPanel.cs   | 60 +++++++++----------
 osu.Game/Overlays/Direct/DirectListPanel.cs   | 47 +++++++--------
 osu.Game/Overlays/Direct/DirectPanel.cs       | 35 +++++------
 6 files changed, 67 insertions(+), 81 deletions(-)

diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
index 9b897b4912..631b0ed56e 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
@@ -6,7 +6,7 @@ using osu.Framework.Graphics.Sprites;
 
 namespace osu.Game.Beatmaps.Drawables
 {
-    internal class BeatmapBackgroundSprite : Sprite
+    public class BeatmapBackgroundSprite : Sprite
     {
         private readonly WorkingBeatmap working;
 
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
index 7c0aa49d2a..e91b52565a 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
@@ -9,7 +9,7 @@ using OpenTK.Graphics;
 
 namespace osu.Game.Beatmaps.Drawables
 {
-    internal class DifficultyColouredContainer : Container, IHasAccentColour
+    public class DifficultyColouredContainer : Container, IHasAccentColour
     {
         public Color4 AccentColour { get; set; }
 
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index a8b63c2502..9df1f0f284 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -11,7 +11,7 @@ using OpenTK.Graphics;
 namespace osu.Game.Beatmaps.Drawables
 {
 
-    internal class DifficultyIcon : DifficultyColouredContainer
+    public class DifficultyIcon : DifficultyColouredContainer
     {
         private readonly BeatmapInfo beatmap;
 
diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index e4faf7f783..87df6f5892 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -1,6 +1,7 @@
 // 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.Collections.Generic;
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
@@ -8,6 +9,8 @@ using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Beatmaps.Drawables;
 using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
@@ -19,20 +22,6 @@ namespace osu.Game.Overlays.Direct
         private readonly float horizontal_padding = 10;
         private readonly float vertical_padding = 5;
 
-        private readonly Sprite background;
-        private readonly OsuSpriteText title, artist, authorPrefix, author, source;
-        private readonly Statistic playCount, favouriteCount;
-        private readonly FillFlowContainer difficultyIcons;
-
-        protected override Sprite Background => background;
-        protected override OsuSpriteText Title => title;
-        protected override OsuSpriteText Artist => artist;
-        protected override OsuSpriteText Author => author;
-        protected override OsuSpriteText Source => source;
-        protected override Statistic PlayCount => playCount;
-        protected override Statistic FavouriteCount => favouriteCount;
-        protected override FillFlowContainer DifficultyIcons => difficultyIcons;
-
         public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
         {
             Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
@@ -45,7 +34,11 @@ namespace osu.Game.Overlays.Direct
                 Radius = 3f,
                 Colour = Color4.Black.Opacity(0.25f),
             };
+        }
 
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours, LocalisationEngine localisation)
+        {
             Children = new Drawable[]
             {
                 new Box
@@ -53,7 +46,7 @@ namespace osu.Game.Overlays.Direct
                     RelativeSizeAxes = Axes.Both,
                     Colour = Color4.Black,
                 },
-                background = new Sprite
+                new Sprite
                 {
                     FillMode = FillMode.Fill,
                 },
@@ -79,13 +72,15 @@ namespace osu.Game.Overlays.Direct
                             Direction = FillDirection.Vertical,
                             Children = new[]
                             {
-                                title = new OsuSpriteText
+                                new OsuSpriteText
                                 {
+                                    Text = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
                                     TextSize = 18,
                                     Font = @"Exo2.0-BoldItalic",
                                 },
-                                artist = new OsuSpriteText
+                                new OsuSpriteText
                                 {
+                                    Text = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
                                     Font = @"Exo2.0-BoldItalic",
                                 },
                             },
@@ -114,29 +109,36 @@ namespace osu.Game.Overlays.Direct
                                             Direction = FillDirection.Horizontal,
                                             Children = new[]
                                             {
-                                                authorPrefix = new OsuSpriteText
+                                                new OsuSpriteText
                                                 {
                                                     Text = @"mapped by ",
                                                     TextSize = 14,
                                                     Shadow = false,
+                                                    Colour = colours.Gray5,
                                                 },
-                                                author = new OsuSpriteText
+                                                new OsuSpriteText
                                                 {
+                                                    Text = SetInfo.Metadata.Author,
                                                     TextSize = 14,
                                                     Font = @"Exo2.0-SemiBoldItalic",
                                                     Shadow = false,
+                                                    Colour = colours.BlueDark,
                                                 },
                                             },
                                         },
-                                        source = new OsuSpriteText
+                                        new OsuSpriteText
                                         {
+                                            Text = $@"from {SetInfo.Metadata.Source}",
                                             TextSize = 14,
                                             Shadow = false,
+                                            Colour = colours.Gray5,
                                         },
-                                        difficultyIcons = new FillFlowContainer
+                                        new FillFlowContainer
                                         {
+                                            AutoSizeAxes = Axes.X,
+                                            Height = 20,
                                             Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding  },
-                                            AutoSizeAxes = Axes.Both,
+                                            Children = DifficultyIcons,
                                         },
                                     },
                                 },
@@ -153,22 +155,14 @@ namespace osu.Game.Overlays.Direct
                     Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
                     Children = new[]
                     {
-                        playCount = new Statistic(FontAwesome.fa_play_circle)
+                        new Statistic(FontAwesome.fa_play_circle)
                         {
-	                        Margin = new MarginPadding { Right = 1 },
+                            Margin = new MarginPadding { Right = 1 },
                         },
-                        favouriteCount = new Statistic(FontAwesome.fa_heart),
+                        new Statistic(FontAwesome.fa_heart),
                     },
                 },
             };
         }
-
-        [BackgroundDependencyLoader]
-        private void load(OsuColour colours)
-        {
-        	authorPrefix.Colour = colours.Gray5;
-        	Author.Colour = colours.BlueDark;
-        	Source.Colour = colours.Gray5;
-        }
     }
 }
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 8de2121c0b..f01e956497 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -11,6 +11,8 @@ using osu.Framework.Graphics.Colour;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Database;
+using osu.Framework.Allocation;
+using osu.Framework.Localisation;
 
 namespace osu.Game.Overlays.Direct
 {
@@ -20,20 +22,6 @@ namespace osu.Game.Overlays.Direct
         private readonly float vertical_padding = 5;
         private readonly float height = 70;
 
-        private readonly Sprite background;
-        private readonly OsuSpriteText title, artist, author, source;
-        private readonly Statistic playCount, favouriteCount;
-        private readonly FillFlowContainer difficultyIcons;
-
-        protected override Sprite Background => background;
-        protected override OsuSpriteText Title => title;
-        protected override OsuSpriteText Artist => artist;
-        protected override OsuSpriteText Author => author;
-        protected override OsuSpriteText Source => source;
-        protected override Statistic PlayCount => playCount;
-        protected override Statistic FavouriteCount => favouriteCount;
-        protected override FillFlowContainer DifficultyIcons => difficultyIcons;
-
         public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap)
         {
             RelativeSizeAxes = Axes.X;
@@ -47,7 +35,11 @@ namespace osu.Game.Overlays.Direct
                 Radius = 3f,
                 Colour = Color4.Black.Opacity(0.25f),
             };
+        }
 
+        [BackgroundDependencyLoader]
+        private void load(LocalisationEngine localisation)
+        {
             Children = new Drawable[]
             {
                 new Box
@@ -55,7 +47,7 @@ namespace osu.Game.Overlays.Direct
                     RelativeSizeAxes = Axes.Both,
                     Colour = Color4.Black,
                 },
-                background = new Sprite
+                new Sprite
                 {
                     FillMode = FillMode.Fill,
                 },
@@ -76,19 +68,23 @@ namespace osu.Game.Overlays.Direct
                             Direction = FillDirection.Vertical,
                             Children = new Drawable[]
                             {
-                                title = new OsuSpriteText
+                                new OsuSpriteText
                                 {
+                                    Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
                                     TextSize = 18,
                                     Font = @"Exo2.0-BoldItalic",
                                 },
-                                artist = new OsuSpriteText
+                                new OsuSpriteText
                                 {
+                                    Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
                                     Font = @"Exo2.0-BoldItalic",
                                 },
-                                difficultyIcons = new FillFlowContainer
+                                new FillFlowContainer
                                 {
+                                    AutoSizeAxes = Axes.X,
+                                    Height = 20,
                                     Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
-                                    AutoSizeAxes = Axes.Both,
+                                    Children = DifficultyIcons,
                                 },
                             },
                         },
@@ -101,11 +97,11 @@ namespace osu.Game.Overlays.Direct
                             Margin = new MarginPadding { Right = (height - vertical_padding * 2) + vertical_padding },
                             Children = new Drawable[]
                             {
-                                playCount = new Statistic(FontAwesome.fa_play_circle)
+                                new Statistic(FontAwesome.fa_play_circle)
                                 {
-                                	Margin = new MarginPadding { Right = 1 },
+                                    Margin = new MarginPadding { Right = 1 },
                                 },
-                                favouriteCount = new Statistic(FontAwesome.fa_heart),
+                                new Statistic(FontAwesome.fa_heart),
                                 new FillFlowContainer
                                 {
                                     Anchor = Anchor.TopRight,
@@ -119,15 +115,17 @@ namespace osu.Game.Overlays.Direct
                                             Text = @"mapped by ",
                                             TextSize = 14,
                                         },
-                                        author = new OsuSpriteText
+                                        new OsuSpriteText
                                         {
+                                            Text = SetInfo.Metadata.Author,
                                             TextSize = 14,
                                             Font = @"Exo2.0-SemiBoldItalic",
                                         },
                                     },
                                 },
-                                source = new OsuSpriteText
+                                new OsuSpriteText
                                 {
+                                    Text = $@"from {SetInfo.Metadata.Source}",
                                     Anchor = Anchor.TopRight,
                                     Origin = Anchor.TopRight,
                                     TextSize = 14,
@@ -143,6 +141,7 @@ namespace osu.Game.Overlays.Direct
                     },
                 },
             };
+            
         }
 
         private class DownloadButton : ClickableContainer
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index b163712e85..9668658ebf 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -1,12 +1,11 @@
 // 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.Collections.Generic;
 using OpenTK;
-using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
-using osu.Framework.Localisation;
 using osu.Game.Beatmaps.Drawables;
 using osu.Game.Database;
 using osu.Game.Graphics;
@@ -16,32 +15,26 @@ namespace osu.Game.Overlays.Direct
 {
     public abstract class DirectPanel : Container
     {
-        protected virtual Sprite Background { get; }
-        protected virtual OsuSpriteText Title { get; }
-        protected virtual OsuSpriteText Artist { get; }
-        protected virtual OsuSpriteText Author { get; }
-        protected virtual OsuSpriteText Source { get; }
-        protected virtual Statistic PlayCount { get; }
-        protected virtual Statistic FavouriteCount { get; }
-        protected virtual FillFlowContainer DifficultyIcons { get; }
+        protected readonly BeatmapSetInfo SetInfo;
 
-        private BeatmapSetInfo setInfo;
+        protected IEnumerable<DifficultyIcon> DifficultyIcons
+        {
+            get
+            {
+                var icons = new List<DifficultyIcon>();
 
-        [BackgroundDependencyLoader]
-        private void load(LocalisationEngine localisation)
-        {
-            Title.Current = localisation.GetUnicodePreference(setInfo.Metadata.TitleUnicode, setInfo.Metadata.Title);
-            Artist.Current = localisation.GetUnicodePreference(setInfo.Metadata.ArtistUnicode, setInfo.Metadata.Artist);
-            Author.Text = setInfo.Metadata.Author;
-            Source.Text = @"from " + setInfo.Metadata.Source;
+                foreach (var b in SetInfo.Beatmaps)
+                    icons.Add(new DifficultyIcon(b));
 
-            foreach (var b in setInfo.Beatmaps)
-                DifficultyIcons.Add(new DifficultyIcon(b));
+                return icons;
+            }
         }
 
+        //todo: Direct panel backgrounds
+
         public DirectPanel(BeatmapSetInfo setInfo)
         {
-            this.setInfo = setInfo;
+            SetInfo = setInfo;
         }
 
         public class Statistic : FillFlowContainer

From e1c4c36122d49181e90ecdd89821bdca143296f2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 15:43:18 -0300
Subject: [PATCH 021/179] +BeatmapOnlineInfo, +OnlineWorkingBeatmap, minor
 cleanups, panel beatmap backgrounds

---
 .../Tests/TestCaseDirect.cs                   | 177 +++++++++++++++---
 osu.Game/Database/BeatmapInfo.cs              |   3 +
 osu.Game/Database/BeatmapOnlineInfo.cs        |  38 ++++
 osu.Game/Database/OnlineWorkingBeatmap.cs     |  37 ++++
 osu.Game/Overlays/Direct/DirectGridPanel.cs   |  39 ++--
 osu.Game/Overlays/Direct/DirectListPanel.cs   |  17 +-
 osu.Game/Overlays/Direct/DirectPanel.cs       |  11 +-
 osu.Game/Overlays/DirectOverlay.cs            |   2 +-
 osu.Game/osu.Game.csproj                      |   2 +
 9 files changed, 284 insertions(+), 42 deletions(-)
 create mode 100644 osu.Game/Database/BeatmapOnlineInfo.cs
 create mode 100644 osu.Game/Database/OnlineWorkingBeatmap.cs

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
index c7fa97952e..cc4dae4e9a 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -39,32 +39,165 @@ namespace osu.Desktop.VisualTests.Tests
 
         private void newBeatmaps()
         {
-            var setInfo = new BeatmapSetInfo
+            var ruleset = rulesets.GetRuleset(0);
+
+            direct.BeatmapSets = new BeatmapSetInfo[]
             {
-                Metadata = new BeatmapMetadata
+                new BeatmapSetInfo
                 {
-                    Title = @"Platina",
-                    Artist = @"Maaya Sakamoto",
-                    Author = @"TicClick",
-                    Source = @"Cardcaptor Sakura",
+                    Metadata = new BeatmapMetadata
+                    {
+                        Title = @"OrVid",
+                        Artist = @"An",
+                        Author = @"RLC",
+                        Source = @"",
+                    },
+                    Beatmaps = new List<BeatmapInfo>
+                    {
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 5.35f,
+                            Metadata = new BeatmapMetadata(),
+                            OnlineInfo = new BeatmapOnlineInfo
+                            {
+                                Covers = new[] { @"https://assets.ppy.sh//beatmaps/578332/covers/cover.jpg?1494591390" },
+                                Preview = @"https://b.ppy.sh/preview/578332.mp3",
+                                PlayCount = 97,
+                                FavouriteCount = 72,
+                            },
+                        },
+                    },
                 },
-                Beatmaps = new List<BeatmapInfo>(),
-            };
-            
-            for (int i = 0; i < 4; i++)
-            {
-                setInfo.Beatmaps.Add(new BeatmapInfo
+                new BeatmapSetInfo
                 {
-                    Ruleset = rulesets.GetRuleset(i),
-                    StarDifficulty = i + 1,
-                });
-            }
-
-            var s = new List<BeatmapSetInfo>();
-            for (int i = 0; i < 10; i++)
-                s.Add(setInfo);
-
-            direct.BeatmapSets = s;
+                    Metadata = new BeatmapMetadata
+                    {
+                        Title = @"tiny lamp",
+                        Artist = @"fhana",
+                        Author = @"Sotarks",
+                        Source = @"ぎんぎつね",
+                    },
+                    Beatmaps = new List<BeatmapInfo>
+                    {
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 5.81f,
+                            Metadata = new BeatmapMetadata(),
+                            OnlineInfo = new BeatmapOnlineInfo
+                            {
+                                Covers = new[] { @"https://assets.ppy.sh//beatmaps/599627/covers/cover.jpg?1494539318" },
+                                Preview = @"https//b.ppy.sh/preview/599627.mp3",
+                                PlayCount = 3082,
+                                FavouriteCount = 14,
+                            },
+                        },
+                    },
+                },
+                new BeatmapSetInfo
+                {
+                    Metadata = new BeatmapMetadata
+                    {
+                        Title = @"At Gwanghwamun",
+                        Artist = @"KYUHYUN",
+                        Author = @"Cerulean Veyron",
+                        Source = @"",
+                    },
+                    Beatmaps = new List<BeatmapInfo>
+                    {
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 0.9f,
+                            Metadata = new BeatmapMetadata(),
+                            OnlineInfo = new BeatmapOnlineInfo
+                            {
+                                Covers = new[] { @"https://assets.ppy.sh//beatmaps/513268/covers/cover.jpg?1494502863" },
+                                Preview = @"https//b.ppy.sh/preview/513268.mp3",
+                                PlayCount = 2762,
+                                FavouriteCount = 15,
+                            },
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 1.1f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 2.02f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 3.49f,
+                        },
+                    },
+                },
+                new BeatmapSetInfo
+                {
+                    Metadata = new BeatmapMetadata
+                    {
+                        Title = @"RHAPSODY OF BLUE SKY",
+                        Artist = @"fhana",
+                        Author = @"[Kamiya]",
+                        Source = @"小林さんちのメイドラゴン",
+                    },
+                    Beatmaps = new List<BeatmapInfo>
+                    {
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 1.26f,
+                            Metadata = new BeatmapMetadata(),
+                            OnlineInfo = new BeatmapOnlineInfo
+                            {
+                                Covers = new[] { @"https://assets.ppy.sh//beatmaps/586841/covers/cover.jpg?1494052741" },
+                                Preview = @"https//b.ppy.sh/preview/586841.mp3",
+                                PlayCount = 62317,
+                                FavouriteCount = 161,
+                            },
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 2.01f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 2.87f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 3.76f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 3.93f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 4.37f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 5.13f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 5.42f,
+                        },
+                    },
+                },
+            };
         }
     }
 }
diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs
index c2e35d64a8..9f253f6055 100644
--- a/osu.Game/Database/BeatmapInfo.cs
+++ b/osu.Game/Database/BeatmapInfo.cs
@@ -43,6 +43,9 @@ namespace osu.Game.Database
         [Ignore]
         public BeatmapMetrics Metrics { get; set; }
 
+        [Ignore]
+        public BeatmapOnlineInfo OnlineInfo { get; set; }
+
         public string Path { get; set; }
 
         [JsonProperty("file_md5")]
diff --git a/osu.Game/Database/BeatmapOnlineInfo.cs b/osu.Game/Database/BeatmapOnlineInfo.cs
new file mode 100644
index 0000000000..889539e697
--- /dev/null
+++ b/osu.Game/Database/BeatmapOnlineInfo.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 Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace osu.Game.Database
+{
+    /// <summary>
+    /// Beatmap info retrieved for non-local viewing.
+    /// </summary>
+    public class BeatmapOnlineInfo
+    {
+        /// <summary>
+        /// The different sizes of cover art for this beatmap: cover, cover@2x, card, card@2x, list, list@2x.
+        /// </summary>
+        [JsonProperty(@"covers")]
+        public IEnumerable<string> Covers { get; set; }
+
+        /// <summary>
+        /// A small sample clip of this beatmap's song.
+        /// </summary>
+        [JsonProperty(@"previewUrl")]
+        public string Preview { get; set; }
+
+        /// <summary>
+        /// The amount of plays this beatmap has.
+        /// </summary>
+        [JsonProperty(@"play_count")]
+        public int PlayCount { get; set; }
+
+        /// <summary>
+        /// The amount of people who have favourited this map.
+        /// </summary>
+        [JsonProperty(@"favourite_count")]
+        public int FavouriteCount { get; set; }
+    }
+}
diff --git a/osu.Game/Database/OnlineWorkingBeatmap.cs b/osu.Game/Database/OnlineWorkingBeatmap.cs
new file mode 100644
index 0000000000..87539d55e4
--- /dev/null
+++ b/osu.Game/Database/OnlineWorkingBeatmap.cs
@@ -0,0 +1,37 @@
+// 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.Linq;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Database
+{
+    internal class OnlineWorkingBeatmap : WorkingBeatmap
+	{
+        private TextureStore textures;
+        private TrackManager tracks;
+
+        public OnlineWorkingBeatmap(BeatmapInfo beatmapInfo, TextureStore textures, TrackManager tracks) : base(beatmapInfo)
+        {
+            this.textures = textures;
+            this.tracks = tracks;
+        }
+
+        protected override Beatmap GetBeatmap()
+        {
+            return new Beatmap();
+        }
+
+        protected override Texture GetBackground()
+        {
+            return textures.Get(BeatmapInfo.OnlineInfo.Covers.FirstOrDefault());
+        }
+
+        protected override Track GetTrack()
+        {
+            return tracks.Get(BeatmapInfo.OnlineInfo.Preview);
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 87df6f5892..9200e53dcf 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -1,7 +1,7 @@
 // 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.Collections.Generic;
+using System.Linq;
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
@@ -9,8 +9,8 @@ using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
 using osu.Framework.Localisation;
-using osu.Game.Beatmaps.Drawables;
 using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
@@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Direct
         }
 
         [BackgroundDependencyLoader]
-        private void load(OsuColour colours, LocalisationEngine localisation)
+        private void load(OsuColour colours, LocalisationEngine localisation, TextureStore textures)
         {
             Children = new Drawable[]
             {
@@ -46,9 +46,13 @@ namespace osu.Game.Overlays.Direct
                     RelativeSizeAxes = Axes.Both,
                     Colour = Color4.Black,
                 },
-                new Sprite
+                new Container
                 {
-                    FillMode = FillMode.Fill,
+                    RelativeSizeAxes = Axes.Both,
+                    Children = new[]
+                    {
+                        GetBackground(textures),
+                    },
                 },
                 new Box
                 {
@@ -126,18 +130,27 @@ namespace osu.Game.Overlays.Direct
                                                 },
                                             },
                                         },
-                                        new OsuSpriteText
+                                        new Container
                                         {
-                                            Text = $@"from {SetInfo.Metadata.Source}",
-                                            TextSize = 14,
-                                            Shadow = false,
-                                            Colour = colours.Gray5,
+                                            AutoSizeAxes = Axes.X,
+                                            Height = 14,
+                                            Children = new[]
+                                            {
+                                                new OsuSpriteText
+                                                {
+                                                    Text = $@"from {SetInfo.Metadata.Source}",
+                                                    TextSize = 14,
+                                                    Shadow = false,
+                                                    Colour = colours.Gray5,
+                                                    Alpha = SetInfo.Metadata.Source == @"" ? 0 : 1,
+                                                },
+                                            },
                                         },
                                         new FillFlowContainer
                                         {
                                             AutoSizeAxes = Axes.X,
                                             Height = 20,
-                                            Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding  },
+                                            Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
                                             Children = DifficultyIcons,
                                         },
                                     },
@@ -155,11 +168,11 @@ namespace osu.Game.Overlays.Direct
                     Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
                     Children = new[]
                     {
-                        new Statistic(FontAwesome.fa_play_circle)
+                        new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
                         {
                             Margin = new MarginPadding { Right = 1 },
                         },
-                        new Statistic(FontAwesome.fa_heart),
+                        new Statistic(FontAwesome.fa_heart, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.FavouriteCount ?? 0),
                     },
                 },
             };
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index f01e956497..a6c23ea7bd 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -13,6 +13,8 @@ using osu.Game.Graphics.Sprites;
 using osu.Game.Database;
 using osu.Framework.Allocation;
 using osu.Framework.Localisation;
+using osu.Framework.Graphics.Textures;
+using System.Linq;
 
 namespace osu.Game.Overlays.Direct
 {
@@ -38,7 +40,7 @@ namespace osu.Game.Overlays.Direct
         }
 
         [BackgroundDependencyLoader]
-        private void load(LocalisationEngine localisation)
+        private void load(LocalisationEngine localisation, TextureStore textures)
         {
             Children = new Drawable[]
             {
@@ -47,9 +49,13 @@ namespace osu.Game.Overlays.Direct
                     RelativeSizeAxes = Axes.Both,
                     Colour = Color4.Black,
                 },
-                new Sprite
+                new Container
                 {
-                    FillMode = FillMode.Fill,
+                    RelativeSizeAxes = Axes.Both,
+                    Children = new[]
+                    {
+                        GetBackground(textures),
+                    },
                 },
                 new Box
                 {
@@ -97,11 +103,11 @@ namespace osu.Game.Overlays.Direct
                             Margin = new MarginPadding { Right = (height - vertical_padding * 2) + vertical_padding },
                             Children = new Drawable[]
                             {
-                                new Statistic(FontAwesome.fa_play_circle)
+                                new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
                                 {
                                     Margin = new MarginPadding { Right = 1 },
                                 },
-                                new Statistic(FontAwesome.fa_heart),
+                                new Statistic(FontAwesome.fa_heart, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.FavouriteCount ?? 0),
                                 new FillFlowContainer
                                 {
                                     Anchor = Anchor.TopRight,
@@ -129,6 +135,7 @@ namespace osu.Game.Overlays.Direct
                                     Anchor = Anchor.TopRight,
                                     Origin = Anchor.TopRight,
                                     TextSize = 14,
+                                    Alpha = SetInfo.Metadata.Source == @"" ? 0 : 1,
                                 },
                             },
                         },
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 9668658ebf..1cf9e549ca 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -2,10 +2,12 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
+using System.Linq;
 using OpenTK;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
 using osu.Game.Beatmaps.Drawables;
 using osu.Game.Database;
 using osu.Game.Graphics;
@@ -30,7 +32,14 @@ namespace osu.Game.Overlays.Direct
             }
         }
 
-        //todo: Direct panel backgrounds
+        protected Drawable GetBackground(TextureStore textures)
+        {
+            return new AsyncLoadWrapper(new Sprite
+            {
+                FillMode = FillMode.Fill,
+                Texture = new OnlineWorkingBeatmap(SetInfo.Beatmaps.FirstOrDefault(), textures, null).Background,
+            }) { RelativeSizeAxes = Axes.Both };
+        }
 
         public DirectPanel(BeatmapSetInfo setInfo)
         {
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 7e167bd2c1..bdb92dce21 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Overlays
                 var p = new List<DirectPanel>();
 
                 foreach (BeatmapSetInfo b in value)
-                    p.Add(new DirectGridPanel(b) { Width = 400 });
+                    p.Add(new DirectListPanel(b));
 
                 panels.Children = p;
             }
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index e69a836829..4f2456e7c9 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -436,6 +436,8 @@
     <Compile Include="Overlays\Direct\DirectPanel.cs" />
     <Compile Include="Overlays\Direct\DirectGridPanel.cs" />
     <Compile Include="Overlays\Direct\DirectListPanel.cs" />
+    <Compile Include="Database\OnlineWorkingBeatmap.cs" />
+    <Compile Include="Database\BeatmapOnlineInfo.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">

From c6320c0f11716a903afc913f5eb506ec76330e60 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 16:02:42 -0300
Subject: [PATCH 022/179] BeatmapBackgroundSprite public -> internal

---
 osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
index 631b0ed56e..9b897b4912 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
@@ -6,7 +6,7 @@ using osu.Framework.Graphics.Sprites;
 
 namespace osu.Game.Beatmaps.Drawables
 {
-    public class BeatmapBackgroundSprite : Sprite
+    internal class BeatmapBackgroundSprite : Sprite
     {
         private readonly WorkingBeatmap working;
 

From a86d07cac7e996ce7efbb034d2b770c4b787d36a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 16:07:05 -0300
Subject: [PATCH 023/179] Remove enclosing background container

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs | 9 +--------
 osu.Game/Overlays/Direct/DirectListPanel.cs | 9 +--------
 2 files changed, 2 insertions(+), 16 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 9200e53dcf..f8a6f1530f 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -46,14 +46,7 @@ namespace osu.Game.Overlays.Direct
                     RelativeSizeAxes = Axes.Both,
                     Colour = Color4.Black,
                 },
-                new Container
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Children = new[]
-                    {
-                        GetBackground(textures),
-                    },
-                },
+                GetBackground(textures),
                 new Box
                 {
                     RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index a6c23ea7bd..d4779c99e1 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -49,14 +49,7 @@ namespace osu.Game.Overlays.Direct
                     RelativeSizeAxes = Axes.Both,
                     Colour = Color4.Black,
                 },
-                new Container
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Children = new[]
-                    {
-                        GetBackground(textures),
-                    },
-                },
+                GetBackground(textures),
                 new Box
                 {
                     RelativeSizeAxes = Axes.Both,

From c3d753a585296f0046efc9ecfce269aded24978c Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 16:11:45 -0300
Subject: [PATCH 024/179] DifficultyIcons -> GetDifficultyIcons()

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs |  2 +-
 osu.Game/Overlays/Direct/DirectListPanel.cs |  2 +-
 osu.Game/Overlays/Direct/DirectPanel.cs     | 13 +++++--------
 3 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index f8a6f1530f..24d4e77490 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Direct
                                             AutoSizeAxes = Axes.X,
                                             Height = 20,
                                             Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
-                                            Children = DifficultyIcons,
+                                            Children = GetDifficultyIcons(),
                                         },
                                     },
                                 },
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index d4779c99e1..0dfd80051d 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Direct
                                     AutoSizeAxes = Axes.X,
                                     Height = 20,
                                     Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
-                                    Children = DifficultyIcons,
+                                    Children = GetDifficultyIcons(),
                                 },
                             },
                         },
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 1cf9e549ca..c15bc6372d 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -19,17 +19,14 @@ namespace osu.Game.Overlays.Direct
     {
         protected readonly BeatmapSetInfo SetInfo;
 
-        protected IEnumerable<DifficultyIcon> DifficultyIcons
+        protected IEnumerable<DifficultyIcon> GetDifficultyIcons()
         {
-            get
-            {
-                var icons = new List<DifficultyIcon>();
+            var icons = new List<DifficultyIcon>();
 
-                foreach (var b in SetInfo.Beatmaps)
-                    icons.Add(new DifficultyIcon(b));
+            foreach (var b in SetInfo.Beatmaps)
+                icons.Add(new DifficultyIcon(b));
 
-                return icons;
-            }
+            return icons;
         }
 
         protected Drawable GetBackground(TextureStore textures)

From 6eac19e76b2b394f611e96bb0fa965d90f35df3a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 16:12:47 -0300
Subject: [PATCH 025/179] Move constructor above methods in DirectPanel

---
 osu.Game/Overlays/Direct/DirectPanel.cs | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index c15bc6372d..5b11326745 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -19,16 +19,21 @@ namespace osu.Game.Overlays.Direct
     {
         protected readonly BeatmapSetInfo SetInfo;
 
+        public DirectPanel(BeatmapSetInfo setInfo)
+        {
+            SetInfo = setInfo;
+        }
+
         protected IEnumerable<DifficultyIcon> GetDifficultyIcons()
         {
             var icons = new List<DifficultyIcon>();
-
+            
             foreach (var b in SetInfo.Beatmaps)
                 icons.Add(new DifficultyIcon(b));
-
+            
             return icons;
         }
-
+        
         protected Drawable GetBackground(TextureStore textures)
         {
             return new AsyncLoadWrapper(new Sprite
@@ -38,11 +43,6 @@ namespace osu.Game.Overlays.Direct
             }) { RelativeSizeAxes = Axes.Both };
         }
 
-        public DirectPanel(BeatmapSetInfo setInfo)
-        {
-            SetInfo = setInfo;
-        }
-
         public class Statistic : FillFlowContainer
         {
             private readonly SpriteText text;

From 74ed4cf0e373feeaf3bff56b25eacd9b3c97579e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 16:19:23 -0300
Subject: [PATCH 026/179] More cleanup

---
 osu.Game/Overlays/Direct/FilterControl.cs | 44 ++++++++++++-----------
 osu.Game/Overlays/Direct/Header.cs        |  3 +-
 2 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 91372d7543..9f792d07ac 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Overlays.Direct
         /// </summary>
         public static readonly float LOWER_HEIGHT = 21;
 
+        private const float padding = 10;
+
         private readonly Box tabStrip;
         private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
         private FillFlowContainer resultCountsContainer;
@@ -68,18 +70,19 @@ namespace osu.Game.Overlays.Direct
                 {
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y,
-                    Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING, Top = 10 },
+                    Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
                     Children = new Drawable[]
                     {
                         Search = new DirectSearchTextBox
                         {
                             RelativeSizeAxes = Axes.X,
+                            Margin = new MarginPadding { Top = padding },
                         },
                         modeButtons = new FillFlowContainer<RulesetToggleButton>
                         {
                             AutoSizeAxes = Axes.Both,
-                            Spacing = new Vector2(10f, 0f),
-                            Margin = new MarginPadding { Top = 10f },
+                            Spacing = new Vector2(padding, 0f),
+                            Margin = new MarginPadding { Top = padding },
                         },
                         SortTabs = new SortTabControl
                         {
@@ -118,7 +121,6 @@ namespace osu.Game.Overlays.Direct
                 },
             };
 
-            //todo: possibly restore from config
             RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
             SortTabs.Current.Value = SortCriteria.Title;
             SortTabs.Current.TriggerChange();
@@ -250,28 +252,12 @@ namespace osu.Game.Overlays.Direct
         }
     }
 
-    public enum RankStatus
-    {
-    	Any,
-    	[Description("Ranked & Approved")]
-    	RankedApproved,
-    	Approved,
-    	Loved,
-    	Favourites,
-    	[Description("Mod Requests")]
-    	ModRequests,
-    	Pending,
-    	Graveyard,
-    	[Description("My Maps")]
-    	MyMaps,
-    }
-
     public class ResultCounts
     {
         public readonly int Artists;
         public readonly int Songs;
         public readonly int Tags;
-
+        
         public ResultCounts(int artists, int songs, int tags)
         {
             Artists = artists;
@@ -279,4 +265,20 @@ namespace osu.Game.Overlays.Direct
             Tags = tags;
         }
     }
+
+    public enum RankStatus
+    {
+        Any,
+        [Description("Ranked & Approved")]
+        RankedApproved,
+        Approved,
+        Loved,
+        Favourites,
+        [Description("Mod Requests")]
+        ModRequests,
+        Pending,
+        Graveyard,
+        [Description("My Maps")]
+        MyMaps,
+    }
 }
diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs
index af1dfb4b53..8e4ede48d5 100644
--- a/osu.Game/Overlays/Direct/Header.cs
+++ b/osu.Game/Overlays/Direct/Header.cs
@@ -81,7 +81,6 @@ namespace osu.Game.Overlays.Direct
                 },
             };
 
-            //todo: possibly restore from config instead
             Tabs.Current.Value = DirectTab.Search;
             Tabs.Current.TriggerChange();
         }
@@ -102,7 +101,7 @@ namespace osu.Game.Overlays.Direct
                 AccentColour = Color4.White;
             }
 
-            private class DirectTabItem : OsuTabControl<DirectTab>.OsuTabItem
+            private class DirectTabItem : OsuTabItem
             {
                 public DirectTabItem(DirectTab value) : base(value)
                 {

From 1bd13a836138ee4e846081457e0132390b791a0b Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 16:22:54 -0300
Subject: [PATCH 027/179] Indentation

---
 osu.Game/Overlays/Direct/SortTabControl.cs | 36 +++++++++++-----------
 osu.Game/Overlays/DirectOverlay.cs         |  2 +-
 2 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/osu.Game/Overlays/Direct/SortTabControl.cs b/osu.Game/Overlays/Direct/SortTabControl.cs
index 5da41e5fc5..c6b6479923 100644
--- a/osu.Game/Overlays/Direct/SortTabControl.cs
+++ b/osu.Game/Overlays/Direct/SortTabControl.cs
@@ -53,24 +53,24 @@ namespace osu.Game.Overlays.Direct
 
                 Children = new Drawable[]
                 {
-                new OsuSpriteText
-                {
-                    Margin = new MarginPadding { Top = 8, Bottom = 8 },
-                    Origin = Anchor.BottomLeft,
-                    Anchor = Anchor.BottomLeft,
-                    Text = (value as Enum)?.GetDescription() ?? value.ToString(),
-                    TextSize = 14,
-                    Font = @"Exo2.0-Bold",
-                },
-                box = new Box
-                {
-                    RelativeSizeAxes = Axes.X,
-                    Height = 5,
-                    Scale = new Vector2(1f, 0f),
-                    Colour = Color4.White,
-                    Origin = Anchor.BottomLeft,
-                    Anchor = Anchor.BottomLeft,
-                },
+                    new OsuSpriteText
+                    {
+                        Margin = new MarginPadding { Top = 8, Bottom = 8 },
+                        Origin = Anchor.BottomLeft,
+                        Anchor = Anchor.BottomLeft,
+                        Text = (value as Enum)?.GetDescription() ?? value.ToString(),
+                        TextSize = 14,
+                        Font = @"Exo2.0-Bold",
+                    },
+                    box = new Box
+                    {
+                        RelativeSizeAxes = Axes.X,
+                        Height = 5,
+                        Scale = new Vector2(1f, 0f),
+                        Colour = Color4.White,
+                        Origin = Anchor.BottomLeft,
+                        Anchor = Anchor.BottomLeft,
+                    },
                 };
             }
 
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index bdb92dce21..b4f57f1df9 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Overlays
                 var p = new List<DirectPanel>();
 
                 foreach (BeatmapSetInfo b in value)
-                    p.Add(new DirectListPanel(b));
+                    p.Add(new DirectListPanel(b)); //todo: proper switching between grid/list
 
                 panels.Children = p;
             }

From 5b2c74be508f32ac68e2ac875dd3a061126a3a74 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 16:24:27 -0300
Subject: [PATCH 028/179] Remove Direct folder from csproj

---
 osu.Game/osu.Game.csproj | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 4f2456e7c9..37dc36f59f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -460,9 +460,7 @@
   <ItemGroup />
   <ItemGroup />
   <ItemGroup />
-  <ItemGroup>
-    <Folder Include="Overlays\Direct\" />
-  </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
        Other similar extension points exist, see Microsoft.Common.targets.

From 1d61fc84c7e44fda5d9323699c83164761bda3a0 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 17:04:59 -0300
Subject: [PATCH 029/179] Remove duplicate code in SettingsEnumDropdown

---
 .../Graphics/UserInterface/OsuEnumDropdown.cs |  6 ++--
 .../Overlays/Settings/SettingsEnumDropdown.cs | 29 +++++--------------
 2 files changed, 10 insertions(+), 25 deletions(-)

diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
index fe828ca65b..5de6507bb3 100644
--- a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
@@ -13,12 +13,12 @@ namespace osu.Game.Graphics.UserInterface
         public OsuEnumDropdown()
         {
             if (!typeof(T).IsEnum)
-                throw new InvalidOperationException("SettingsDropdown only supports enums as the generic type argument");
-
+                throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument");
+
             List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
             foreach(var val in (T[])Enum.GetValues(typeof(T)))
             {
-                var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
+                var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
                 items.Add(
                     new KeyValuePair<string, T>(
                         field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
index a9f0848403..ffd8c04c50 100644
--- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
+++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
@@ -1,32 +1,17 @@
 // 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 System.Reflection;
-using System.ComponentModel;
-using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.UserInterface;
 
 namespace osu.Game.Overlays.Settings
 {
     public class SettingsEnumDropdown<T> : SettingsDropdown<T>
     {
-        public SettingsEnumDropdown()
-        {
-            if (!typeof(T).IsEnum)
-                throw new InvalidOperationException("SettingsDropdown only supports enums as the generic type argument");
-
-            List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
-            foreach(var val in (T[])Enum.GetValues(typeof(T)))
-            {
-                var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
-                items.Add(
-                    new KeyValuePair<string, T>(
-                        field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
-                        val
-                    )
-                );
-            }
-            Items = items;
-        }
+        protected override Drawable CreateControl() => new OsuEnumDropdown<T>
+		{
+			Margin = new MarginPadding { Top = 5 },
+			RelativeSizeAxes = Axes.X,
+        };
     }
 }

From 05b8fc5126a0617475f055114c01ee6bb2b6bc9a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 17:52:34 -0300
Subject: [PATCH 030/179] Added switching between grid/list and little
 transitions for the panels

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs | 17 ++++-
 osu.Game/Overlays/Direct/DirectListPanel.cs |  7 ++
 osu.Game/Overlays/Direct/FilterControl.cs   | 85 +++++++++++++++++++--
 osu.Game/Overlays/DirectOverlay.cs          | 22 ++++--
 4 files changed, 116 insertions(+), 15 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 24d4e77490..65967915d6 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -22,11 +22,14 @@ namespace osu.Game.Overlays.Direct
         private readonly float horizontal_padding = 10;
         private readonly float vertical_padding = 5;
 
+        private FillFlowContainer bottomPanel;
+
         public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
         {
             Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
             CornerRadius = 4;
             Masking = true;
+
             EdgeEffect = new EdgeEffect
             {
                 Type = EdgeEffectType.Shadow,
@@ -36,6 +39,16 @@ namespace osu.Game.Overlays.Direct
             };
         }
 
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            FadeInFromZero(200, EasingTypes.Out);
+            bottomPanel.LayoutDuration = 200;
+            bottomPanel.LayoutEasing = EasingTypes.Out;
+            bottomPanel.Origin = Anchor.BottomLeft;
+        }
+
         [BackgroundDependencyLoader]
         private void load(OsuColour colours, LocalisationEngine localisation, TextureStore textures)
         {
@@ -52,10 +65,10 @@ namespace osu.Game.Overlays.Direct
                     RelativeSizeAxes = Axes.Both,
                     Colour = Color4.Black.Opacity(0.5f),
                 },
-                new FillFlowContainer
+                bottomPanel = new FillFlowContainer
                 {
                     Anchor = Anchor.BottomLeft,
-                    Origin = Anchor.BottomLeft,
+                    Origin = Anchor.TopLeft,
                     Direction = FillDirection.Vertical,
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y,
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 0dfd80051d..e107561388 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -39,6 +39,13 @@ namespace osu.Game.Overlays.Direct
             };
         }
 
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            FadeInFromZero(200, EasingTypes.Out);
+        }
+
         [BackgroundDependencyLoader]
         private void load(LocalisationEngine localisation, TextureStore textures)
         {
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 9f792d07ac..8a874e3fd0 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -31,15 +31,16 @@ namespace osu.Game.Overlays.Direct
 
         private readonly Box tabStrip;
         private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
-        private FillFlowContainer resultCountsContainer;
-        private OsuSpriteText resultCountsText;
+        private readonly FillFlowContainer resultCountsContainer;
+        private readonly OsuSpriteText resultCountsText;
 
         public readonly SearchTextBox Search;
         public readonly SortTabControl SortTabs;
         public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
+        public readonly Bindable<PanelDisplayStyle> DisplayStyle = new Bindable<PanelDisplayStyle>();
 
         private ResultCounts resultCounts;
-        public  ResultCounts ResultCounts
+        public ResultCounts ResultCounts
         {
             get { return resultCounts; }
             set { resultCounts = value; updateResultCounts(); }
@@ -49,6 +50,7 @@ namespace osu.Game.Overlays.Direct
         {
             RelativeSizeAxes = Axes.X;
             AutoSizeAxes = Axes.Y;
+            DisplayStyle.Value = PanelDisplayStyle.Grid;
 
             Children = new Drawable[]
             {
@@ -90,13 +92,33 @@ namespace osu.Game.Overlays.Direct
                         },
                     },
                 },
-                RankStatusDropdown = new SlimEnumDropdown<RankStatus>
+                new FillFlowContainer
                 {
+                    AutoSizeAxes = Axes.Both,
                     Anchor = Anchor.BottomRight,
                     Origin = Anchor.BottomRight,
-                    RelativeSizeAxes = Axes.None,
+                    Spacing = new Vector2(10f, 0f),
+                    Direction = FillDirection.Horizontal,
                     Margin = new MarginPadding { Bottom = 5, Right = DirectOverlay.WIDTH_PADDING },
-                    Width = 160f,
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Spacing = new Vector2(5f, 0f),
+                            Direction = FillDirection.Horizontal,
+                            Children = new[]
+                            {
+                                new DisplayModeToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle),
+                                new DisplayModeToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle),
+                            },
+                        },
+                        RankStatusDropdown = new SlimEnumDropdown<RankStatus>
+                        {
+                            RelativeSizeAxes = Axes.None,
+                            Width = 160f,
+                        },
+                    },
                 },
                 resultCountsContainer = new FillFlowContainer
                 {
@@ -117,7 +139,7 @@ namespace osu.Game.Overlays.Direct
                             TextSize = 15,
                             Font = @"Exo2.0-Bold",
                         },
-                    }    
+                    }
                 },
             };
 
@@ -221,6 +243,47 @@ namespace osu.Game.Overlays.Direct
             }
         }
 
+        private class DisplayModeToggleButton : ClickableContainer
+        {
+            private readonly TextAwesome icon;
+            private readonly PanelDisplayStyle mode;
+            private readonly Bindable<PanelDisplayStyle> bindable;
+
+            public DisplayModeToggleButton(FontAwesome icon, PanelDisplayStyle mode, Bindable<PanelDisplayStyle> bindable)
+            {
+                this.bindable = bindable;
+                this.mode = mode;
+                Size = new Vector2(25f);
+
+                Children = new Drawable[]
+                {
+                    this.icon = new TextAwesome
+                    {
+                        Anchor = Anchor.Centre,
+                        Origin = Anchor.Centre,
+                        Icon = icon,
+                        TextSize = 18,
+                        UseFullGlyphHeight = false,
+                        Alpha = 0.5f,
+                    },
+                };
+
+                bindable.ValueChanged += Bindable_ValueChanged;
+                Bindable_ValueChanged(bindable.Value);
+                Action = () => bindable.Value = this.mode;
+            }
+
+            private void Bindable_ValueChanged(PanelDisplayStyle mode)
+            {
+                icon.FadeTo(mode == this.mode ? 1.0f : 0.5f, 100);
+            }
+
+            protected override void Dispose(bool isDisposing)
+            {
+                bindable.ValueChanged -= Bindable_ValueChanged;
+            }
+        }
+
         private class SlimEnumDropdown<T> : OsuEnumDropdown<T>
         {
             protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
@@ -257,7 +320,7 @@ namespace osu.Game.Overlays.Direct
         public readonly int Artists;
         public readonly int Songs;
         public readonly int Tags;
-        
+
         public ResultCounts(int artists, int songs, int tags)
         {
             Artists = artists;
@@ -281,4 +344,10 @@ namespace osu.Game.Overlays.Direct
         [Description("My Maps")]
         MyMaps,
     }
+
+    public enum PanelDisplayStyle
+    {
+        Grid,
+        List,
+    }
 }
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index b4f57f1df9..02ea4694c1 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -22,16 +22,16 @@ namespace osu.Game.Overlays
         private readonly FilterControl filter;
         private readonly FillFlowContainer<DirectPanel> panels;
 
+        private IEnumerable<BeatmapSetInfo> beatmapSets;
         public IEnumerable<BeatmapSetInfo> BeatmapSets
         {
+            get { return beatmapSets; }
             set
             {
-                var p = new List<DirectPanel>();
+                if (beatmapSets == value) return;
+                beatmapSets = value;
 
-                foreach (BeatmapSetInfo b in value)
-                    p.Add(new DirectListPanel(b)); //todo: proper switching between grid/list
-
-                panels.Children = p;
+                recreatePanels(filter.DisplayStyle.Value);
             }
         }
 
@@ -110,6 +110,18 @@ namespace osu.Game.Overlays
             };
 
             filter.Search.Exit = Hide;
+            filter.DisplayStyle.ValueChanged += recreatePanels;
+        }
+
+        private void recreatePanels(PanelDisplayStyle displayStyle)
+        {
+            var p = new List<DirectPanel>();
+
+            foreach (BeatmapSetInfo b in BeatmapSets)
+                p.Add(displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)(new DirectGridPanel(b) { Width = 400}) :
+                                                               (DirectPanel)(new DirectListPanel(b)));
+
+            panels.Children = p;
         }
 
         protected override void PopIn()

From 2f10b72cb2ff308a3f959628b3b8ddb4253b1ae2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 18:32:04 -0300
Subject: [PATCH 031/179] Proper dropdown expansion, DisplayModeToggleButton ->
 DisplayStyleToggleButton

---
 osu.Game/Overlays/Direct/FilterControl.cs | 28 ++++++++++++-----------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 8a874e3fd0..8195f9f6ce 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -46,10 +46,12 @@ namespace osu.Game.Overlays.Direct
             set { resultCounts = value; updateResultCounts(); }
         }
 
+        protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || RankStatusDropdown.Contains(screenSpacePos);
+
         public FilterControl()
         {
             RelativeSizeAxes = Axes.X;
-            AutoSizeAxes = Axes.Y;
+            Height = 35 + 32 + 30 + (padding * 2); // search + mode toggle buttons + sort tabs + padding
             DisplayStyle.Value = PanelDisplayStyle.Grid;
 
             Children = new Drawable[]
@@ -95,11 +97,11 @@ namespace osu.Game.Overlays.Direct
                 new FillFlowContainer
                 {
                     AutoSizeAxes = Axes.Both,
-                    Anchor = Anchor.BottomRight,
-                    Origin = Anchor.BottomRight,
+                    Anchor = Anchor.TopRight,
+                    Origin = Anchor.TopRight,
                     Spacing = new Vector2(10f, 0f),
                     Direction = FillDirection.Horizontal,
-                    Margin = new MarginPadding { Bottom = 5, Right = DirectOverlay.WIDTH_PADDING },
+                    Margin = new MarginPadding { Top = Height - 25 - padding, Right = DirectOverlay.WIDTH_PADDING },
                     Children = new Drawable[]
                     {
                         new FillFlowContainer
@@ -109,8 +111,8 @@ namespace osu.Game.Overlays.Direct
                             Direction = FillDirection.Horizontal,
                             Children = new[]
                             {
-                                new DisplayModeToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle),
-                                new DisplayModeToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle),
+                                new DisplayStyleToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle),
+                                new DisplayStyleToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle),
                             },
                         },
                         RankStatusDropdown = new SlimEnumDropdown<RankStatus>
@@ -243,16 +245,16 @@ namespace osu.Game.Overlays.Direct
             }
         }
 
-        private class DisplayModeToggleButton : ClickableContainer
+        private class DisplayStyleToggleButton : ClickableContainer
         {
             private readonly TextAwesome icon;
-            private readonly PanelDisplayStyle mode;
+            private readonly PanelDisplayStyle style;
             private readonly Bindable<PanelDisplayStyle> bindable;
 
-            public DisplayModeToggleButton(FontAwesome icon, PanelDisplayStyle mode, Bindable<PanelDisplayStyle> bindable)
+            public DisplayStyleToggleButton(FontAwesome icon, PanelDisplayStyle style, Bindable<PanelDisplayStyle> bindable)
             {
                 this.bindable = bindable;
-                this.mode = mode;
+                this.style = style;
                 Size = new Vector2(25f);
 
                 Children = new Drawable[]
@@ -270,12 +272,12 @@ namespace osu.Game.Overlays.Direct
 
                 bindable.ValueChanged += Bindable_ValueChanged;
                 Bindable_ValueChanged(bindable.Value);
-                Action = () => bindable.Value = this.mode;
+                Action = () => bindable.Value = this.style;
             }
 
-            private void Bindable_ValueChanged(PanelDisplayStyle mode)
+            private void Bindable_ValueChanged(PanelDisplayStyle style)
             {
-                icon.FadeTo(mode == this.mode ? 1.0f : 0.5f, 100);
+                icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100);
             }
 
             protected override void Dispose(bool isDisposing)

From 649fc8362bc8694647df2c1630de06a343e35ba1 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 18:54:47 -0300
Subject: [PATCH 032/179] Fix ranked status dropdown expanding below the panels

---
 osu.Game/Overlays/DirectOverlay.cs | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 02ea4694c1..5dcad5e8d0 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
+using System.Linq;
 using OpenTK;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
@@ -81,7 +82,7 @@ namespace osu.Game.Overlays
                     Padding = new MarginPadding { Top = Header.HEIGHT },
                     Children = new Drawable[]
                     {
-                        new FillFlowContainer
+                        new ContentFlow
                         {
                             RelativeSizeAxes = Axes.X,
                             AutoSizeAxes = Axes.Y,
@@ -118,7 +119,7 @@ namespace osu.Game.Overlays
             var p = new List<DirectPanel>();
 
             foreach (BeatmapSetInfo b in BeatmapSets)
-                p.Add(displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)(new DirectGridPanel(b) { Width = 400}) :
+                p.Add(displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)(new DirectGridPanel(b) { Width = 400 }) :
                                                                (DirectPanel)(new DirectListPanel(b)));
 
             panels.Children = p;
@@ -138,5 +139,16 @@ namespace osu.Game.Overlays
 
             filter.Search.HoldFocus = false;
         }
+
+        private class ContentFlow : FillFlowContainer<Drawable>
+        {
+            protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
+            protected override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Reverse();
+
+            public ContentFlow()
+            {
+                Direction = FillDirection.Vertical;
+            }
+        }
     }
 }

From 6cc7602db1dea88df5cecfdaa1ee360c4f96fc72 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 19:02:53 -0300
Subject: [PATCH 033/179] Added download button animations

---
 osu.Game/Overlays/Direct/DirectListPanel.cs | 28 ++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index e107561388..434bdb01e0 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -15,6 +15,7 @@ using osu.Framework.Allocation;
 using osu.Framework.Localisation;
 using osu.Framework.Graphics.Textures;
 using System.Linq;
+using osu.Framework.Input;
 
 namespace osu.Game.Overlays.Direct
 {
@@ -153,12 +154,14 @@ namespace osu.Game.Overlays.Direct
 
         private class DownloadButton : ClickableContainer
         {
+            private readonly TextAwesome icon;
+
             //todo: proper download button animations
             public DownloadButton()
             {
                 Children = new Drawable[]
                 {
-                    new TextAwesome
+                    icon = new TextAwesome
                     {
                         Anchor = Anchor.Centre,
                         Origin = Anchor.Centre,
@@ -168,6 +171,29 @@ namespace osu.Game.Overlays.Direct
                     },
                 };
             }
+
+            protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+            {
+                icon.ScaleTo(0.9f, 1000, EasingTypes.Out);
+                return base.OnMouseDown(state, args);
+            }
+
+            protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+            {
+                icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
+                return base.OnMouseUp(state, args);
+            }
+
+            protected override bool OnHover(InputState state)
+            {
+                icon.ScaleTo(1.1f, 500, EasingTypes.OutElastic);
+                return base.OnHover(state);
+            }
+
+            protected override void OnHoverLost(InputState state)
+            {
+                icon.ScaleTo(1, 500, EasingTypes.OutElastic);
+            }
         }
     }
 }

From 57115f453ea198843dc0b403c4b4214bcd8f608a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 19:03:45 -0300
Subject: [PATCH 034/179] Remove todo comment

---
 osu.Game/Overlays/Direct/DirectListPanel.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 434bdb01e0..aa28ca642e 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -156,7 +156,6 @@ namespace osu.Game.Overlays.Direct
         {
             private readonly TextAwesome icon;
 
-            //todo: proper download button animations
             public DownloadButton()
             {
                 Children = new Drawable[]

From 7a60aa614dc4eb4a1ba7a98d457e50ca5d4eb08a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 19:05:54 -0300
Subject: [PATCH 035/179] Reword comment

---
 osu.Game/Database/BeatmapOnlineInfo.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Database/BeatmapOnlineInfo.cs b/osu.Game/Database/BeatmapOnlineInfo.cs
index 889539e697..bcb2ef16ac 100644
--- a/osu.Game/Database/BeatmapOnlineInfo.cs
+++ b/osu.Game/Database/BeatmapOnlineInfo.cs
@@ -7,7 +7,7 @@ using System.Collections.Generic;
 namespace osu.Game.Database
 {
     /// <summary>
-    /// Beatmap info retrieved for non-local viewing.
+    /// Beatmap info retrieved for previewing locally without having the beatmap downloaded.
     /// </summary>
     public class BeatmapOnlineInfo
     {

From c2ea2bb5b075f0d6f760970f9ed284a484b8c9ba Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 19:13:07 -0300
Subject: [PATCH 036/179] Proper search tab changing logic

---
 osu.Game/Overlays/DirectOverlay.cs | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 5dcad5e8d0..81aa6b2ff5 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -20,6 +20,7 @@ namespace osu.Game.Overlays
         private readonly float panel_padding = 10f;
 
         private readonly Box background;
+        private readonly Header header;
         private readonly FilterControl filter;
         private readonly FillFlowContainer<DirectPanel> panels;
 
@@ -104,13 +105,16 @@ namespace osu.Game.Overlays
                         },
                     },
                 },
-                new Header
+                header = new Header
                 {
                     RelativeSizeAxes = Axes.X,
                 },
             };
 
+            header.Tabs.Current.ValueChanged += (tab) => { if (tab != DirectTab.Search) filter.Search.Current.Value = @""; };
+
             filter.Search.Exit = Hide;
+            filter.Search.Current.ValueChanged += (s) => { if (s != @"") header.Tabs.Current.Value = DirectTab.Search; };
             filter.DisplayStyle.ValueChanged += recreatePanels;
         }
 
@@ -140,13 +144,13 @@ namespace osu.Game.Overlays
             filter.Search.HoldFocus = false;
         }
 
-        private class ContentFlow : FillFlowContainer<Drawable>
+        private class ContentFlow : FillFlowContainer<Drawable>
         {
-            protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
+            protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
             protected override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Reverse();
 
             public ContentFlow()
-            {
+            {
                 Direction = FillDirection.Vertical;
             }
         }

From 0a90965a5ba5381721b888a90359d6cdfb5838a2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 19:22:42 -0300
Subject: [PATCH 037/179] CI formatting

---
 osu.Game/Database/OnlineWorkingBeatmap.cs     |  2 +-
 osu.Game/Overlays/Direct/DirectListPanel.cs   | 41 +++++++++----------
 osu.Game/Overlays/Direct/DirectPanel.cs       |  2 +-
 .../Overlays/Settings/SettingsEnumDropdown.cs |  8 ++--
 4 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/osu.Game/Database/OnlineWorkingBeatmap.cs b/osu.Game/Database/OnlineWorkingBeatmap.cs
index 87539d55e4..dcbe21f33c 100644
--- a/osu.Game/Database/OnlineWorkingBeatmap.cs
+++ b/osu.Game/Database/OnlineWorkingBeatmap.cs
@@ -9,7 +9,7 @@ using osu.Game.Beatmaps;
 namespace osu.Game.Database
 {
     internal class OnlineWorkingBeatmap : WorkingBeatmap
-	{
+    {
         private TextureStore textures;
         private TrackManager tracks;
 
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index aa28ca642e..a17597d10a 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -149,7 +149,6 @@ namespace osu.Game.Overlays.Direct
                     },
                 },
             };
-            
         }
 
         private class DownloadButton : ClickableContainer
@@ -171,26 +170,26 @@ namespace osu.Game.Overlays.Direct
                 };
             }
 
-            protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
-            {
-                icon.ScaleTo(0.9f, 1000, EasingTypes.Out);
-                return base.OnMouseDown(state, args);
-            }
-
-            protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
-            {
-                icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
-                return base.OnMouseUp(state, args);
-            }
-
-            protected override bool OnHover(InputState state)
-            {
-                icon.ScaleTo(1.1f, 500, EasingTypes.OutElastic);
-                return base.OnHover(state);
-            }
-
-            protected override void OnHoverLost(InputState state)
-            {
+            protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+            {
+                icon.ScaleTo(0.9f, 1000, EasingTypes.Out);
+                return base.OnMouseDown(state, args);
+            }
+
+            protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+            {
+                icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
+                return base.OnMouseUp(state, args);
+            }
+
+            protected override bool OnHover(InputState state)
+            {
+                icon.ScaleTo(1.1f, 500, EasingTypes.OutElastic);
+                return base.OnHover(state);
+            }
+
+            protected override void OnHoverLost(InputState state)
+            {
                 icon.ScaleTo(1, 500, EasingTypes.OutElastic);
             }
         }
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 5b11326745..016fb755a6 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Direct
         protected IEnumerable<DifficultyIcon> GetDifficultyIcons()
         {
             var icons = new List<DifficultyIcon>();
-            
+
             foreach (var b in SetInfo.Beatmaps)
                 icons.Add(new DifficultyIcon(b));
             
diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
index ffd8c04c50..5725ee8439 100644
--- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
+++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
@@ -8,10 +8,10 @@ namespace osu.Game.Overlays.Settings
 {
     public class SettingsEnumDropdown<T> : SettingsDropdown<T>
     {
-        protected override Drawable CreateControl() => new OsuEnumDropdown<T>
-		{
-			Margin = new MarginPadding { Top = 5 },
-			RelativeSizeAxes = Axes.X,
+        protected override Drawable CreateControl() => new OsuEnumDropdown<T>
+        {
+            Margin = new MarginPadding { Top = 5 },
+            RelativeSizeAxes = Axes.X,
         };
     }
 }

From 0a96aaf5759d98c39657e47f63353f69b674a5c1 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 19:27:51 -0300
Subject: [PATCH 038/179] Whitespace

---
 osu.Game/Overlays/Direct/DirectPanel.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 016fb755a6..ba424cee03 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -30,10 +30,10 @@ namespace osu.Game.Overlays.Direct
 
             foreach (var b in SetInfo.Beatmaps)
                 icons.Add(new DifficultyIcon(b));
-            
+
             return icons;
         }
-        
+
         protected Drawable GetBackground(TextureStore textures)
         {
             return new AsyncLoadWrapper(new Sprite

From c981a4a511ac56b4c9704db60f3e9b8acf00fc4a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 19:50:45 -0300
Subject: [PATCH 039/179] Formatting

---
 .../Tests/TestCaseDirect.cs                   |  5 +---
 osu.Game/Database/OnlineWorkingBeatmap.cs     |  4 ++--
 osu.Game/Overlays/Direct/DirectGridPanel.cs   |  6 ++---
 osu.Game/Overlays/Direct/DirectListPanel.cs   | 12 +++++-----
 osu.Game/Overlays/Direct/DirectPanel.cs       |  2 +-
 osu.Game/Overlays/Direct/FilterControl.cs     | 24 +++++++++----------
 osu.Game/Overlays/Direct/SortTabControl.cs    |  4 ++--
 osu.Game/Overlays/DirectOverlay.cs            | 17 +++++++------
 8 files changed, 35 insertions(+), 39 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
index cc4dae4e9a..c68796a9ab 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -3,12 +3,9 @@
 
 using System.Collections.Generic;
 using osu.Framework.Allocation;
-using osu.Framework.Graphics;
 using osu.Framework.Testing;
 using osu.Game.Database;
-using osu.Game.Graphics;
 using osu.Game.Overlays;
-using osu.Game.Overlays.Dialog;
 using osu.Game.Overlays.Direct;
 
 namespace osu.Desktop.VisualTests.Tests
@@ -41,7 +38,7 @@ namespace osu.Desktop.VisualTests.Tests
         {
             var ruleset = rulesets.GetRuleset(0);
 
-            direct.BeatmapSets = new BeatmapSetInfo[]
+            direct.BeatmapSets = new[]
             {
                 new BeatmapSetInfo
                 {
diff --git a/osu.Game/Database/OnlineWorkingBeatmap.cs b/osu.Game/Database/OnlineWorkingBeatmap.cs
index dcbe21f33c..1465c59434 100644
--- a/osu.Game/Database/OnlineWorkingBeatmap.cs
+++ b/osu.Game/Database/OnlineWorkingBeatmap.cs
@@ -10,8 +10,8 @@ namespace osu.Game.Database
 {
     internal class OnlineWorkingBeatmap : WorkingBeatmap
     {
-        private TextureStore textures;
-        private TrackManager tracks;
+        private readonly TextureStore textures;
+        private readonly TrackManager tracks;
 
         public OnlineWorkingBeatmap(BeatmapInfo beatmapInfo, TextureStore textures, TrackManager tracks) : base(beatmapInfo)
         {
diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 65967915d6..01793df1f0 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -19,8 +19,8 @@ namespace osu.Game.Overlays.Direct
 {
     public class DirectGridPanel : DirectPanel
     {
-        private readonly float horizontal_padding = 10;
-        private readonly float vertical_padding = 5;
+        private const float horizontal_padding = 10;
+        private const float vertical_padding = 5;
 
         private FillFlowContainer bottomPanel;
 
@@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Direct
         [BackgroundDependencyLoader]
         private void load(OsuColour colours, LocalisationEngine localisation, TextureStore textures)
         {
-            Children = new Drawable[]
+            Children = new[]
             {
                 new Box
                 {
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index a17597d10a..7f215b7447 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -21,9 +21,9 @@ namespace osu.Game.Overlays.Direct
 {
     public class DirectListPanel : DirectPanel
     {
-        private readonly float horizontal_padding = 10;
-        private readonly float vertical_padding = 5;
-        private readonly float height = 70;
+        private const float horizontal_padding = 10;
+        private const float vertical_padding = 5;
+        private const float height = 70;
 
         public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap)
         {
@@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Direct
         [BackgroundDependencyLoader]
         private void load(LocalisationEngine localisation, TextureStore textures)
         {
-            Children = new Drawable[]
+            Children = new[]
             {
                 new Box
                 {
@@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Direct
                             Origin = Anchor.TopRight,
                             AutoSizeAxes = Axes.Both,
                             Direction = FillDirection.Vertical,
-                            Margin = new MarginPadding { Right = (height - vertical_padding * 2) + vertical_padding },
+                            Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding },
                             Children = new Drawable[]
                             {
                                 new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
@@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Direct
                         {
                             Anchor = Anchor.TopRight,
                             Origin = Anchor.TopRight,
-                            Size = new Vector2(height - (vertical_padding * 2)),
+                            Size = new Vector2(height - vertical_padding * 2),
                         },
                     },
                 },
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index ba424cee03..6959a3f421 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Direct
     {
         protected readonly BeatmapSetInfo SetInfo;
 
-        public DirectPanel(BeatmapSetInfo setInfo)
+        protected DirectPanel(BeatmapSetInfo setInfo)
         {
             SetInfo = setInfo;
         }
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 8195f9f6ce..7ea1f2a8e1 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Direct
         public FilterControl()
         {
             RelativeSizeAxes = Axes.X;
-            Height = 35 + 32 + 30 + (padding * 2); // search + mode toggle buttons + sort tabs + padding
+            Height = 35 + 32 + 30 + padding * 2; // search + mode toggle buttons + sort tabs + padding
             DisplayStyle.Value = PanelDisplayStyle.Grid;
 
             Children = new Drawable[]
@@ -170,9 +170,9 @@ namespace osu.Game.Overlays.Direct
             resultCountsContainer.FadeTo(ResultCounts == null ? 0 : 1, 200, EasingTypes.Out);
             if (resultCounts == null) return;
 
-            resultCountsText.Text = pluralize(@"Artist", ResultCounts.Artists) + ", " +
-                                    pluralize(@"Song", ResultCounts.Songs) + ", " +
-                                    pluralize(@"Tag", ResultCounts.Tags);
+            resultCountsText.Text = pluralize(@"Artist", ResultCounts?.Artists ?? 0) + ", " +
+                                    pluralize(@"Song", ResultCounts?.Songs ?? 0) + ", " +
+                                    pluralize(@"Tag", ResultCounts?.Tags ?? 0);
         }
 
         private string pluralize(string prefix, int value)
@@ -196,7 +196,7 @@ namespace osu.Game.Overlays.Direct
 
         private class RulesetToggleButton : ClickableContainer
         {
-            private TextAwesome icon;
+            private readonly TextAwesome icon;
 
             private RulesetInfo ruleset;
             public RulesetInfo Ruleset
@@ -209,11 +209,11 @@ namespace osu.Game.Overlays.Direct
                 }
             }
 
-            private Bindable<RulesetInfo> bindable;
+            private readonly Bindable<RulesetInfo> bindable;
 
-            void Bindable_ValueChanged(RulesetInfo obj)
+            private void Bindable_ValueChanged(RulesetInfo obj)
             {
-                icon.FadeTo((Ruleset.ID == obj?.ID) ? 1f : 0.5f, 100);
+                icon.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100);
             }
 
             public RulesetToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)
@@ -280,7 +280,7 @@ namespace osu.Game.Overlays.Direct
                 icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100);
             }
 
-            protected override void Dispose(bool isDisposing)
+            protected override void Dispose(bool isDisposing)
             {
                 bindable.ValueChanged -= Bindable_ValueChanged;
             }
@@ -347,9 +347,9 @@ namespace osu.Game.Overlays.Direct
         MyMaps,
     }
 
-    public enum PanelDisplayStyle
-    {
+    public enum PanelDisplayStyle
+    {
         Grid,
-        List,
+        List,
     }
 }
diff --git a/osu.Game/Overlays/Direct/SortTabControl.cs b/osu.Game/Overlays/Direct/SortTabControl.cs
index c6b6479923..7eb6ba4e88 100644
--- a/osu.Game/Overlays/Direct/SortTabControl.cs
+++ b/osu.Game/Overlays/Direct/SortTabControl.cs
@@ -27,9 +27,9 @@ namespace osu.Game.Overlays.Direct
 
         private class SortTabItem : TabItem<SortCriteria>
         {
-            private readonly float transition_duration = 100;
+            private const float transition_duration = 100;
 
-            private Box box;
+            private readonly Box box;
 
             public override bool Active
             {
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 81aa6b2ff5..46d19c724b 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -17,10 +17,8 @@ namespace osu.Game.Overlays
     public class DirectOverlay : WaveOverlayContainer
     {
         public static readonly int WIDTH_PADDING = 80;
-        private readonly float panel_padding = 10f;
+        private const float panel_padding = 10f;
 
-        private readonly Box background;
-        private readonly Header header;
         private readonly FilterControl filter;
         private readonly FillFlowContainer<DirectPanel> panels;
 
@@ -30,7 +28,7 @@ namespace osu.Game.Overlays
             get { return beatmapSets; }
             set
             {
-                if (beatmapSets == value) return;
+                if (value == beatmapSets) return;
                 beatmapSets = value;
 
                 recreatePanels(filter.DisplayStyle.Value);
@@ -54,9 +52,10 @@ namespace osu.Game.Overlays
             ThirdWaveColour = OsuColour.FromHex(@"005774");
             FourthWaveColour = OsuColour.FromHex(@"003a4e");
 
+            Header header = null;
             Children = new Drawable[]
             {
-                background = new Box
+                new Box
                 {
                     RelativeSizeAxes = Axes.Both,
                     Colour = OsuColour.FromHex(@"485e74"),
@@ -111,10 +110,10 @@ namespace osu.Game.Overlays
                 },
             };
 
-            header.Tabs.Current.ValueChanged += (tab) => { if (tab != DirectTab.Search) filter.Search.Current.Value = @""; };
+            header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = @""; };
 
             filter.Search.Exit = Hide;
-            filter.Search.Current.ValueChanged += (s) => { if (s != @"") header.Tabs.Current.Value = DirectTab.Search; };
+            filter.Search.Current.ValueChanged += text => { if (text != @"") header.Tabs.Current.Value = DirectTab.Search; };
             filter.DisplayStyle.ValueChanged += recreatePanels;
         }
 
@@ -123,8 +122,8 @@ namespace osu.Game.Overlays
             var p = new List<DirectPanel>();
 
             foreach (BeatmapSetInfo b in BeatmapSets)
-                p.Add(displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)(new DirectGridPanel(b) { Width = 400 }) :
-                                                               (DirectPanel)(new DirectListPanel(b)));
+                p.Add(displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } :
+                                                               new DirectListPanel(b));
 
             panels.Children = p;
         }

From 83c81c06260f70c8edfcc65adae740df96f81998 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 20:03:07 -0300
Subject: [PATCH 040/179] Cleanup (again)

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs |  2 +-
 osu.Game/Overlays/Direct/DirectListPanel.cs |  4 ++--
 osu.Game/Overlays/Direct/SortTabControl.cs  |  2 +-
 osu.Game/Overlays/DirectOverlay.cs          | 10 ++--------
 osu.Game/osu.Game.csproj                    |  1 -
 5 files changed, 6 insertions(+), 13 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 01793df1f0..fc63045e15 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Direct
                                                     TextSize = 14,
                                                     Shadow = false,
                                                     Colour = colours.Gray5,
-                                                    Alpha = SetInfo.Metadata.Source == @"" ? 0 : 1,
+                                                    Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
                                                 },
                                             },
                                         },
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 7f215b7447..02930a9e7b 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Direct
                                     Anchor = Anchor.TopRight,
                                     Origin = Anchor.TopRight,
                                     TextSize = 14,
-                                    Alpha = SetInfo.Metadata.Source == @"" ? 0 : 1,
+                                    Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
                                 },
                             },
                         },
@@ -190,7 +190,7 @@ namespace osu.Game.Overlays.Direct
 
             protected override void OnHoverLost(InputState state)
             {
-                icon.ScaleTo(1, 500, EasingTypes.OutElastic);
+                icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
             }
         }
     }
diff --git a/osu.Game/Overlays/Direct/SortTabControl.cs b/osu.Game/Overlays/Direct/SortTabControl.cs
index 7eb6ba4e88..4d4e02d875 100644
--- a/osu.Game/Overlays/Direct/SortTabControl.cs
+++ b/osu.Game/Overlays/Direct/SortTabControl.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Direct
                         Margin = new MarginPadding { Top = 8, Bottom = 8 },
                         Origin = Anchor.BottomLeft,
                         Anchor = Anchor.BottomLeft,
-                        Text = (value as Enum)?.GetDescription() ?? value.ToString(),
+                        Text = (value as Enum).GetDescription() ?? value.ToString(),
                         TextSize = 14,
                         Font = @"Exo2.0-Bold",
                     },
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 46d19c724b..9f2d56a2dc 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Overlays
             ThirdWaveColour = OsuColour.FromHex(@"005774");
             FourthWaveColour = OsuColour.FromHex(@"003a4e");
 
-            Header header = null;
+            Header header;
             Children = new Drawable[]
             {
                 new Box
@@ -119,13 +119,7 @@ namespace osu.Game.Overlays
 
         private void recreatePanels(PanelDisplayStyle displayStyle)
         {
-            var p = new List<DirectPanel>();
-
-            foreach (BeatmapSetInfo b in BeatmapSets)
-                p.Add(displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } :
-                                                               new DirectListPanel(b));
-
-            panels.Children = p;
+            panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
         }
 
         protected override void PopIn()
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 37dc36f59f..c7b3290a08 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -460,7 +460,6 @@
   <ItemGroup />
   <ItemGroup />
   <ItemGroup />
-  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
        Other similar extension points exist, see Microsoft.Common.targets.

From e7a64126a39ccf3d30729940c67418bc99e501b3 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 20:13:59 -0300
Subject: [PATCH 041/179] Formatting and remove magic number

---
 osu.Game/Overlays/Direct/DirectPanel.cs   | 2 +-
 osu.Game/Overlays/Direct/FilterControl.cs | 8 +++++---
 osu.Game/Overlays/DirectOverlay.cs        | 2 +-
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 6959a3f421..0ea2e48073 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Direct
                 set
                 {
                     this.value = value;
-                    text.Text = string.Format("{0:n0}", Value);
+                    text.Text = string.Format(@"{0:n0}", Value);
                 }
             }
 
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 7ea1f2a8e1..28450718b2 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Direct
                     Origin = Anchor.TopRight,
                     Spacing = new Vector2(10f, 0f),
                     Direction = FillDirection.Horizontal,
-                    Margin = new MarginPadding { Top = Height - 25 - padding, Right = DirectOverlay.WIDTH_PADDING },
+                    Margin = new MarginPadding { Top = Height - SlimEnumDropdown<DirectTab>.HEIGHT - padding, Right = DirectOverlay.WIDTH_PADDING },
                     Children = new Drawable[]
                     {
                         new FillFlowContainer
@@ -255,7 +255,7 @@ namespace osu.Game.Overlays.Direct
             {
                 this.bindable = bindable;
                 this.style = style;
-                Size = new Vector2(25f);
+                Size = new Vector2(SlimEnumDropdown<DirectTab>.HEIGHT);
 
                 Children = new Drawable[]
                 {
@@ -288,6 +288,8 @@ namespace osu.Game.Overlays.Direct
 
         private class SlimEnumDropdown<T> : OsuEnumDropdown<T>
         {
+            public const float HEIGHT = 25;
+
             protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
             protected override Menu CreateMenu() => new SlimMenu();
 
@@ -295,7 +297,7 @@ namespace osu.Game.Overlays.Direct
             {
                 public SlimDropdownHeader()
                 {
-                    Height = 25;
+                    Height = HEIGHT;
                     Icon.TextSize = 16;
                     Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
                 }
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 9f2d56a2dc..ea56d656aa 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Overlays
             get { return beatmapSets; }
             set
             {
-                if (value == beatmapSets) return;
+                if (beatmapSets?.Equals(value) ?? false) return;
                 beatmapSets = value;
 
                 recreatePanels(filter.DisplayStyle.Value);

From cdd2b54dc7543d85a413f8db8c43b7f1398cc7c0 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 20:25:08 -0300
Subject: [PATCH 042/179] Move SlimEnumDropdown into it's own file

---
 osu.Game/Overlays/Direct/FilterControl.cs    | 33 ---------------
 osu.Game/Overlays/Direct/SlimEnumDropdown.cs | 43 ++++++++++++++++++++
 osu.Game/osu.Game.csproj                     |  1 +
 3 files changed, 44 insertions(+), 33 deletions(-)
 create mode 100644 osu.Game/Overlays/Direct/SlimEnumDropdown.cs

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 28450718b2..4c082d4d76 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -10,7 +10,6 @@ using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.UserInterface;
 using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
@@ -285,38 +284,6 @@ namespace osu.Game.Overlays.Direct
                 bindable.ValueChanged -= Bindable_ValueChanged;
             }
         }
-
-        private class SlimEnumDropdown<T> : OsuEnumDropdown<T>
-        {
-            public const float HEIGHT = 25;
-
-            protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
-            protected override Menu CreateMenu() => new SlimMenu();
-
-            private class SlimDropdownHeader : OsuDropdownHeader
-            {
-                public SlimDropdownHeader()
-                {
-                    Height = HEIGHT;
-                    Icon.TextSize = 16;
-                    Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
-                }
-
-                protected override void LoadComplete()
-                {
-                    base.LoadComplete();
-                    BackgroundColour = Color4.Black.Opacity(0.25f);
-                }
-            }
-
-            private class SlimMenu : OsuMenu
-            {
-                public SlimMenu()
-                {
-                    Background.Colour = Color4.Black.Opacity(0.25f);
-                }
-            }
-        }
     }
 
     public class ResultCounts
diff --git a/osu.Game/Overlays/Direct/SlimEnumDropdown.cs b/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
new file mode 100644
index 0000000000..f77a60887a
--- /dev/null
+++ b/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
@@ -0,0 +1,43 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
+    {
+        public const float HEIGHT = 25;
+
+        protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
+        protected override Menu CreateMenu() => new SlimMenu();
+
+        private class SlimDropdownHeader : OsuDropdownHeader
+        {
+            public SlimDropdownHeader()
+            {
+                Height = HEIGHT;
+                Icon.TextSize = 16;
+                Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
+            }
+
+            protected override void LoadComplete()
+            {
+                base.LoadComplete();
+                BackgroundColour = Color4.Black.Opacity(0.25f);
+            }
+        }
+
+        private class SlimMenu : OsuMenu
+        {
+            public SlimMenu()
+            {
+                Background.Colour = Color4.Black.Opacity(0.25f);
+            }
+        }
+    }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index c7b3290a08..a362306938 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -438,6 +438,7 @@
     <Compile Include="Overlays\Direct\DirectListPanel.cs" />
     <Compile Include="Database\OnlineWorkingBeatmap.cs" />
     <Compile Include="Database\BeatmapOnlineInfo.cs" />
+    <Compile Include="Overlays\Direct\SlimEnumDropdown.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">

From 503ee97a15872748e77b1636f3d91b1263924095 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 20:28:37 -0300
Subject: [PATCH 043/179] Fix license header

---
 osu.Game/Overlays/Direct/SlimEnumDropdown.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Direct/SlimEnumDropdown.cs b/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
index f77a60887a..db37dbfc49 100644
--- a/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
+++ b/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
@@ -1,5 +1,5 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK.Graphics;
 using osu.Framework.Extensions.Color4Extensions;

From ffb3450dc2e3935ffa73fb207ec436800b24d09c Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 20:34:51 -0300
Subject: [PATCH 044/179] Remove <U+FEFF> character

---
 osu.Game/Overlays/Direct/DirectPanel.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 0ea2e48073..cdf2dbb0df 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// 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.Collections.Generic;

From 6e7a09fedbea9c214ad66b8c9d7a31083ed706bf Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 20:37:13 -0300
Subject: [PATCH 045/179] Line endings, use ToString for statistic value

---
 osu.Game/Overlays/Direct/DirectPanel.cs      |  2 +-
 osu.Game/Overlays/Direct/SlimEnumDropdown.cs | 32 ++++++++++----------
 2 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index cdf2dbb0df..d349a65d57 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Direct
                 set
                 {
                     this.value = value;
-                    text.Text = string.Format(@"{0:n0}", Value);
+                    text.Text = Value.ToString(@"N0");
                 }
             }
 
diff --git a/osu.Game/Overlays/Direct/SlimEnumDropdown.cs b/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
index db37dbfc49..1d12b8477b 100644
--- a/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
+++ b/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK.Graphics;
@@ -9,14 +9,14 @@ using osu.Game.Graphics.UserInterface;
 
 namespace osu.Game.Overlays.Direct
 {
-    public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
-    {
-        public const float HEIGHT = 25;
-
-        protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
-        protected override Menu CreateMenu() => new SlimMenu();
-
-        private class SlimDropdownHeader : OsuDropdownHeader
+    public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
+    {
+        public const float HEIGHT = 25;
+
+        protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
+        protected override Menu CreateMenu() => new SlimMenu();
+
+        private class SlimDropdownHeader : OsuDropdownHeader
         {
             public SlimDropdownHeader()
             {
@@ -29,15 +29,15 @@ namespace osu.Game.Overlays.Direct
             {
                 base.LoadComplete();
                 BackgroundColour = Color4.Black.Opacity(0.25f);
-            }
-        }
-
-        private class SlimMenu : OsuMenu
+            }
+        }
+
+        private class SlimMenu : OsuMenu
         {
             public SlimMenu()
             {
-                Background.Colour = Color4.Black.Opacity(0.25f);
-            }
-        }
+                Background.Colour = Color4.Black.Opacity(0.25f);
+            }
+        }
     }
 }

From 68cb23786a0c9a1f96926118d65b0513289c1652 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 20:53:51 -0300
Subject: [PATCH 046/179] Fade in background when loaded

---
 osu.Game/Overlays/Direct/DirectPanel.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index d349a65d57..9aae719d5c 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -40,6 +40,7 @@ namespace osu.Game.Overlays.Direct
             {
                 FillMode = FillMode.Fill,
                 Texture = new OnlineWorkingBeatmap(SetInfo.Beatmaps.FirstOrDefault(), textures, null).Background,
+                OnLoadComplete = d => d.FadeInFromZero(400, EasingTypes.Out),
             }) { RelativeSizeAxes = Axes.Both };
         }
 

From e03057343600fe3eebde48ab889902cfe515248c Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 23:44:36 -0300
Subject: [PATCH 047/179] Add ReverseDepthFillFlowContainer to remove code
 duplication

---
 .../Containers/ReverseDepthFillFlowContainer.cs  | 16 ++++++++++++++++
 osu.Game/Overlays/DirectOverlay.cs               | 14 ++------------
 .../Select/Options/BeatmapOptionsOverlay.cs      | 16 ++--------------
 osu.Game/osu.Game.csproj                         |  1 +
 4 files changed, 21 insertions(+), 26 deletions(-)
 create mode 100644 osu.Game/Graphics/Containers/ReverseDepthFillFlowContainer.cs

diff --git a/osu.Game/Graphics/Containers/ReverseDepthFillFlowContainer.cs b/osu.Game/Graphics/Containers/ReverseDepthFillFlowContainer.cs
new file mode 100644
index 0000000000..2b52b06abc
--- /dev/null
+++ b/osu.Game/Graphics/Containers/ReverseDepthFillFlowContainer.cs
@@ -0,0 +1,16 @@
+// 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.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Game.Graphics.Containers
+{
+    public class ReverseDepthFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable
+    {
+        protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
+        protected override IEnumerable<T> FlowingChildren => base.FlowingChildren.Reverse();
+    }
+}
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index ea56d656aa..8c38dffb14 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites;
 using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
 using osu.Game.Overlays.Direct;
 
 namespace osu.Game.Overlays
@@ -82,7 +83,7 @@ namespace osu.Game.Overlays
                     Padding = new MarginPadding { Top = Header.HEIGHT },
                     Children = new Drawable[]
                     {
-                        new ContentFlow
+                        new ReverseDepthFillFlowContainer<Drawable>
                         {
                             RelativeSizeAxes = Axes.X,
                             AutoSizeAxes = Axes.Y,
@@ -136,16 +137,5 @@ namespace osu.Game.Overlays
 
             filter.Search.HoldFocus = false;
         }
-
-        private class ContentFlow : FillFlowContainer<Drawable>
-        {
-            protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
-            protected override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Reverse();
-
-            public ContentFlow()
-            {
-                Direction = FillDirection.Vertical;
-            }
-        }
     }
 }
diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
index c064a0272e..be2f5196ef 100644
--- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
+++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
@@ -2,8 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System;
-using System.Collections.Generic;
-using System.Linq;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
@@ -12,6 +10,7 @@ using osu.Game.Graphics;
 using OpenTK;
 using OpenTK.Graphics;
 using OpenTK.Input;
+using osu.Game.Graphics.Containers;
 
 namespace osu.Game.Screens.Select.Options
 {
@@ -71,7 +70,7 @@ namespace osu.Game.Screens.Select.Options
                     Scale = new Vector2(1, 0),
                     Colour = Color4.Black.Opacity(0.5f),
                 },
-                buttonsContainer = new ButtonFlow
+                buttonsContainer = new ReverseDepthFillFlowContainer<BeatmapOptionsButton>
                 {
                     Height = height,
                     RelativePositionAxes = Axes.X,
@@ -109,16 +108,5 @@ namespace osu.Game.Screens.Select.Options
                 HotKey = hotkey
             });
         }
-
-        private class ButtonFlow : FillFlowContainer<BeatmapOptionsButton>
-        {
-            protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
-            protected override IEnumerable<BeatmapOptionsButton> FlowingChildren => base.FlowingChildren.Reverse();
-
-            public ButtonFlow()
-            {
-                Direction = FillDirection.Horizontal;
-            }
-        }
     }
 }
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index a362306938..fcf22ba053 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -439,6 +439,7 @@
     <Compile Include="Database\OnlineWorkingBeatmap.cs" />
     <Compile Include="Database\BeatmapOnlineInfo.cs" />
     <Compile Include="Overlays\Direct\SlimEnumDropdown.cs" />
+    <Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">

From 0c5fec975da1686a9048e02fb0f751db68a07835 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 02:56:00 -0300
Subject: [PATCH 048/179] Change ModDisplay to use
 ReverseDepthFillFlowContainer

---
 osu.Game/Screens/Play/HUD/ModDisplay.cs | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs
index 92c40f6351..ce6dfae24f 100644
--- a/osu.Game/Screens/Play/HUD/ModDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods;
 using osu.Game.Rulesets.UI;
 using OpenTK;
 using osu.Framework.Input;
+using osu.Game.Graphics.Containers;
 
 namespace osu.Game.Screens.Play.HUD
 {
@@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play.HUD
         {
             Children = new Drawable[]
             {
-                iconsContainer = new IconFlow
+                iconsContainer = new ReverseDepthFillFlowContainer<ModIcon>
                 {
                     Anchor = Anchor.TopCentre,
                     Origin = Anchor.TopCentre,
@@ -93,12 +94,5 @@ namespace osu.Game.Screens.Play.HUD
             contract();
             base.OnHoverLost(state);
         }
-
-        private class IconFlow : FillFlowContainer<ModIcon>
-        {
-            // just reverses the depth of flow contents.
-            protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
-            protected override IEnumerable<ModIcon> FlowingChildren => base.FlowingChildren.Reverse();
-        }
     }
 }

From 1eb96e07bd54fb24b649d002ed0275fa6530aa5a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 03:01:09 -0300
Subject: [PATCH 049/179] Remove unused using directive

---
 osu.Game/Screens/Play/HUD/ModDisplay.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs
index ce6dfae24f..4a10438cdb 100644
--- a/osu.Game/Screens/Play/HUD/ModDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
-using System.Linq;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;

From 4e83f12f34c8287a08dcec547ae5251285d55372 Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Sat, 20 May 2017 11:02:42 -0500
Subject: [PATCH 050/179] Initial implementation Note this won't work with the
 osu!bgm because it is not a beatmap

---
 osu.Game/Screens/Menu/MainMenu.cs        |  3 +-
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 95 ++++++++++++++++++++++++
 osu.Game/osu.Game.csproj                 |  1 +
 3 files changed, 98 insertions(+), 1 deletion(-)
 create mode 100644 osu.Game/Screens/Menu/MenuSideFlashes.cs

diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 71d020b0f2..269c20fff9 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -54,7 +54,8 @@ namespace osu.Game.Screens.Menu
                             OnSolo = delegate { Push(consumeSongSelect()); },
                             OnMulti = delegate { Push(new Lobby()); },
                             OnExit = delegate { Exit(); },
-                        }
+                        },
+                        new MenuSideFlashes(),
                     }
                 }
             };
diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
new file mode 100644
index 0000000000..fa308dc2a6
--- /dev/null
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -0,0 +1,95 @@
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Game.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Timing;
+using System;
+
+namespace osu.Game.Screens.Menu
+{
+    public class MenuSideFlashes : BeatSyncedContainer
+    {
+        public override bool HandleInput => false;
+
+        private Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
+
+        private Box leftBox;
+        private Box rightBox;
+
+        private const int amplitude_dead_zone = 9000;
+        private const float alpha_multiplier = (short.MaxValue - amplitude_dead_zone) / 0.55f;
+        private const int box_max_alpha = 200;
+        private const double box_fade_in_time = 65;
+
+        public MenuSideFlashes()
+        {
+            RelativeSizeAxes = Axes.Both;
+            Anchor = Anchor.Centre;
+            Origin = Anchor.Centre;
+            BlendingMode = BlendingMode.Additive;
+            Children = new Drawable[]
+            {
+                leftBox = new Box
+                {
+                    Anchor = Anchor.CentreLeft,
+                    Origin = Anchor.CentreLeft,
+                    RelativeSizeAxes = Axes.Y,
+                    Width = 300,
+                    Alpha = 0,
+                    BlendingMode = BlendingMode.Additive,
+                    ColourInfo = ColourInfo.GradientHorizontal(new Color4(255, 255, 255, box_max_alpha), Color4.Transparent),
+                },
+                rightBox = new Box
+                {
+                    Anchor = Anchor.CentreRight,
+                    Origin = Anchor.CentreRight,
+                    RelativeSizeAxes = Axes.Y,
+                    Width = 300,
+                    Alpha = 0,
+                    BlendingMode = BlendingMode.Additive,
+                    ColourInfo = ColourInfo.GradientHorizontal(Color4.Transparent, new Color4(255, 255, 255, box_max_alpha)),
+                }
+            };
+        }
+
+        protected override void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
+        {
+            if (!beatmap?.Value?.Track?.IsRunning ?? false)
+            {
+                leftBox.FadeOut(50);
+                rightBox.FadeOut(50);
+            }
+            else if (newBeat >= 0)
+            {
+                short[] lev = beatmap.Value.Track.ChannelPeakAmplitudes;
+                bool nextIsLeft = newBeat % 2 == 0;
+                if (kiai ? nextIsLeft : newBeat % (int)timeSignature == 0)
+                {
+                    leftBox.ClearTransforms();
+                    leftBox.FadeTo(Math.Max(0, (lev[0] - amplitude_dead_zone) / alpha_multiplier), 65);
+                    using (leftBox.BeginDelayedSequence(box_fade_in_time))
+                        leftBox.FadeOut(beatLength, EasingTypes.In);
+                    leftBox.DelayReset();
+                }
+                if (kiai ? !nextIsLeft : newBeat % (int)timeSignature == 0)
+                {
+                    rightBox.ClearTransforms();
+                    rightBox.FadeTo(Math.Max(0, (lev[1] - amplitude_dead_zone) / alpha_multiplier), 65);
+                    using (rightBox.BeginDelayedSequence(box_fade_in_time))
+                        rightBox.FadeOut(beatLength, EasingTypes.In);
+                    rightBox.DelayReset();
+                }
+            }
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuGameBase game)
+        {
+            beatmap = game.Beatmap;
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 18c483fe40..8fd7b398a2 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -183,6 +183,7 @@
     <Compile Include="Database\RulesetDatabase.cs" />
     <Compile Include="Rulesets\Scoring\Score.cs" />
     <Compile Include="Rulesets\Scoring\ScoreProcessor.cs" />
+    <Compile Include="Screens\Menu\MenuSideFlashes.cs" />
     <Compile Include="Screens\Play\HUD\HealthDisplay.cs" />
     <Compile Include="Screens\Play\HUDOverlay.cs" />
     <Compile Include="Screens\Play\HUD\StandardHealthDisplay.cs" />

From 49eb096b03cb846f6039c26aa4a1b279d61c592b Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 13:19:50 -0300
Subject: [PATCH 051/179] Fix visual test mode toggle buttons activation state

---
 osu.Game/Overlays/Direct/FilterControl.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 4c082d4d76..b252eb03a5 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -158,9 +158,10 @@ namespace osu.Game.Overlays.Direct
             resultCountsContainer.Colour = colours.Yellow;
             RankStatusDropdown.AccentColour = colours.BlueDark;
 
+            var b = new Bindable<RulesetInfo>(); //backup bindable incase the game is null
             foreach (var r in rulesets.AllRulesets)
             {
-                modeButtons.Add(new RulesetToggleButton(game?.Ruleset ?? new Bindable<RulesetInfo>(), r));
+                modeButtons.Add(new RulesetToggleButton(game?.Ruleset ?? b, r));
             }
         }
 

From d5904499f740b0604a9e83f041162e85c7d0055a Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Sat, 20 May 2017 11:41:16 -0500
Subject: [PATCH 052/179] Post merge fixes

---
 osu.Game/osu.Game.csproj | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index ee906caa9b..49f453a495 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -78,6 +78,7 @@
     <Compile Include="Beatmaps\Events\BreakEvent.cs" />
     <Compile Include="Beatmaps\Events\Event.cs" />
     <Compile Include="Beatmaps\Events\EventInfo.cs" />
+    <Compile Include="Graphics\Containers\BeatSyncedContainer.cs" />
     <Compile Include="Online\API\Requests\PostMessageRequest.cs" />
     <Compile Include="Online\Chat\ErrorMessage.cs" />
     <Compile Include="Overlays\Chat\ChatTabControl.cs" />
@@ -186,6 +187,7 @@
     <Compile Include="Database\RulesetDatabase.cs" />
     <Compile Include="Rulesets\Scoring\Score.cs" />
     <Compile Include="Rulesets\Scoring\ScoreProcessor.cs" />
+    <Compile Include="Screens\Menu\MenuSideFlashes.cs" />
     <Compile Include="Screens\Play\HUD\HealthDisplay.cs" />
     <Compile Include="Screens\Play\HUDOverlay.cs" />
     <Compile Include="Screens\Play\HUD\StandardHealthDisplay.cs" />

From 38a566890f392e946321c8d4cb134d8b671190b6 Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Sat, 20 May 2017 11:44:19 -0500
Subject: [PATCH 053/179] Add licence header

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index fa308dc2a6..079b3c92c4 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -1,4 +1,7 @@
-using OpenTK.Graphics;
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;

From 87ace2d7ec9b08784f406a4051189b6e0d439d3f Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 13:52:51 -0300
Subject: [PATCH 054/179] @"" -> string.Empty

---
 osu.Game/Overlays/Direct/FilterControl.cs | 2 +-
 osu.Game/Overlays/DirectOverlay.cs        | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index b252eb03a5..b35938f3a6 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -177,7 +177,7 @@ namespace osu.Game.Overlays.Direct
 
         private string pluralize(string prefix, int value)
         {
-            return $@"{value} {prefix}" + (value == 1 ? @"" : @"s");
+            return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
         }
 
         private class DirectSearchTextBox : SearchTextBox
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 8c38dffb14..d6fb1faa8d 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -111,10 +111,10 @@ namespace osu.Game.Overlays
                 },
             };
 
-            header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = @""; };
+            header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; };
 
             filter.Search.Exit = Hide;
-            filter.Search.Current.ValueChanged += text => { if (text != @"") header.Tabs.Current.Value = DirectTab.Search; };
+            filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
             filter.DisplayStyle.ValueChanged += recreatePanels;
         }
 

From eb77c94150597d1c9ee1b7626d1069642ce804c1 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sun, 21 May 2017 15:30:10 -0300
Subject: [PATCH 055/179] Fix search field losing focus

---
 osu.Game/Overlays/DirectOverlay.cs | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index d6fb1faa8d..5c80fed575 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -7,6 +7,7 @@ using OpenTK;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
 using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
@@ -123,12 +124,17 @@ namespace osu.Game.Overlays
             panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
         }
 
+        protected override bool OnFocus(InputState state)
+        {
+            filter.Search.TriggerFocus();
+            return false;
+        }
+
         protected override void PopIn()
         {
             base.PopIn();
 
             filter.Search.HoldFocus = true;
-            Schedule(() => filter.Search.TriggerFocus());
         }
 
         protected override void PopOut()

From 8c77f856674cdbc6798784409b3d80fe28702553 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sun, 21 May 2017 18:52:30 -0300
Subject: [PATCH 056/179] Fix DirectOverlay expanding under the Toolbar

---
 osu.Game/OsuGame.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 5bbf28e2ef..33dbe0a12a 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -164,7 +164,7 @@ namespace osu.Game
             });
 
             //overlay elements
-            LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, overlayContent.Add);
+            LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
             LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add);
             LoadComponentAsync(settings = new SettingsOverlay { Depth = -1 }, overlayContent.Add);
             LoadComponentAsync(musicController = new MusicController

From 4e2126dca8430302250ebff773247664171008fd Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sun, 21 May 2017 18:54:35 -0300
Subject: [PATCH 057/179] Fix nullref when changing display mode without any
 BeatmapSets added

---
 osu.Game/Overlays/DirectOverlay.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 5c80fed575..c56d995c8a 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -121,6 +121,7 @@ namespace osu.Game.Overlays
 
         private void recreatePanels(PanelDisplayStyle displayStyle)
         {
+            if (BeatmapSets == null) return;
             panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
         }
 

From bc980b60ac1d762b593f0e14daaf352b58ec1b0b Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sun, 21 May 2017 22:36:46 -0300
Subject: [PATCH 058/179] Add files to project, update with framework changes

---
 .../Tests/TestCaseMultiRoomPanel.cs                | 12 ++++++------
 .../osu.Desktop.VisualTests.csproj                 |  1 +
 osu.Game/Screens/Multiplayer/MultiRoomPanel.cs     | 14 +++++++-------
 osu.Game/osu.Game.csproj                           |  1 +
 4 files changed, 15 insertions(+), 13 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
index 29cc4481a9..a6dbcda8f9 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
@@ -3,7 +3,6 @@
 
 using OpenTK;
 using OpenTK.Graphics;
-using osu.Framework.Screens.Testing;
 using osu.Game.Graphics.UserInterface;
 using osu.Game.Screens.Select;
 using osu.Game.Screens.Multiplayer;
@@ -13,14 +12,15 @@ using osu.Framework.Graphics.Primitives;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Input;
 using osu.Game.Graphics.Sprites;
+using osu.Framework.Testing;
 
 namespace osu.Desktop.VisualTests.Tests
 {
     class TestCaseMultiRoomPanel : TestCase
     {
         private MultiRoomPanel test;
-        private FlowContainer panelContainer;
-        public override string Name => @"MultiRoomPanel";
+        private FillFlowContainer panelContainer;
+
         public override string Description => @"Select your favourite room";
 
         private void action(int action)
@@ -37,14 +37,14 @@ namespace osu.Desktop.VisualTests.Tests
         {
             base.Reset();
 
-            AddButton(@"ChangeState", () => action(0));
+            AddStep(@"ChangeState", () => action(0));
 
-            Add(panelContainer = new FlowContainer //Positionning container
+            Add(panelContainer = new FillFlowContainer //Positionning container
             {
                 RelativeSizeAxes = Axes.Both,
                 Anchor = Anchor.Centre,
                 Origin = Anchor.Centre,
-                Direction = FlowDirections.Vertical,
+                Direction = FillDirection.Vertical,
                 Size = new Vector2(0.4f, 0.5f),
                 Children = new Drawable[]
                 {
diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
index 135e4596c7..a2228ca9aa 100644
--- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
+++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
@@ -217,6 +217,7 @@
     <Compile Include="Tests\TestCaseLeaderboard.cs" />
     <Compile Include="Beatmaps\TestWorkingBeatmap.cs" />
     <Compile Include="Tests\TestCaseBeatmapDetailArea.cs" />
+    <Compile Include="Tests\TestCaseMultiRoomPanel.cs" />
   </ItemGroup>
   <ItemGroup />
   <ItemGroup />
diff --git a/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs b/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
index 174ec19f75..cbc4cc1182 100644
--- a/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
+++ b/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
@@ -4,12 +4,12 @@
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework;
+using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Primitives;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Input;
-using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
 using osu.Game.Graphics.Sprites;
 
@@ -132,12 +132,12 @@ namespace osu.Game.Screens.Multiplayer
                     RelativeSizeAxes = Axes.Both,
                 }
                 ,
-                new FlowContainer
+                new FillFlowContainer
                 {
                     RelativeSizeAxes = Axes.Both,
                     Anchor = Anchor.TopRight,
                     Origin = Anchor.TopRight,
-                    Direction = FlowDirections.Vertical,
+                    Direction = FillDirection.Vertical,
                     Size = new Vector2(0.75f,1),
 
                     Children = new Drawable[]
@@ -148,11 +148,11 @@ namespace osu.Game.Screens.Multiplayer
                             TextSize = 18,
                             Margin = new MarginPadding { Top = CONTENT_PADDING },
                         },
-                        new FlowContainer
+                        new FillFlowContainer
                         {
                             RelativeSizeAxes = Axes.X,
                             Height = 20,
-                            Direction = FlowDirections.Horizontal,
+                            Direction = FillDirection.Horizontal,
                             Spacing = new Vector2(5,0),
                             Children = new Drawable[]
                             {
@@ -225,10 +225,10 @@ namespace osu.Game.Screens.Multiplayer
                             Colour = statusColour,
                             Margin = new MarginPadding { Top = 10 }
                         },
-                        new FlowContainer
+                        new FillFlowContainer
                         {
                             RelativeSizeAxes = Axes.X,
-                            Direction = FlowDirections.Horizontal,
+                            Direction = FillDirection.Horizontal,
                             Children = new Drawable[]
                             {
                                 new OsuSpriteText
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 2a1195135a..6cc05bf327 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -427,6 +427,7 @@
     <Compile Include="Overlays\Music\PlaylistOverlay.cs" />
     <Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
     <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
+    <Compile Include="Screens\Multiplayer\MultiRoomPanel.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">

From e5ee7096f8a37e7b1dd205eeee7184ae34e0dcca Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 00:07:15 -0300
Subject: [PATCH 059/179] Initial cleanup

---
 .../Tests/TestCaseDrawableMultiplayerRoom.cs  |  60 ++++
 .../Tests/TestCaseMultiRoomPanel.cs           |  70 -----
 .../osu.Desktop.VisualTests.csproj            |   2 +-
 .../Online/Multiplayer/MultiplayerRoom.cs     |  26 ++
 .../Multiplayer/DrawableMultiplayerRoom.cs    | 215 ++++++++++++++
 .../Screens/Multiplayer/MultiRoomPanel.cs     | 269 ------------------
 osu.Game/osu.Game.csproj                      |   6 +-
 7 files changed, 307 insertions(+), 341 deletions(-)
 create mode 100644 osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
 delete mode 100644 osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
 create mode 100644 osu.Game/Online/Multiplayer/MultiplayerRoom.cs
 create mode 100644 osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
 delete mode 100644 osu.Game/Screens/Multiplayer/MultiRoomPanel.cs

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
new file mode 100644
index 0000000000..c9a0dfa280
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
@@ -0,0 +1,60 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Screens.Select;
+using osu.Game.Screens.Multiplayer;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Graphics.Sprites;
+using osu.Framework.Testing;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Users;
+using osu.Game.Database;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+    class TestCaseDrawableMultiplayerRoom : TestCase
+    {
+        public override string Description => @"Select your favourite room";
+
+        public override void Reset()
+        {
+            base.Reset();
+
+            DrawableMultiplayerRoom p;
+            Add(new FillFlowContainer
+            {
+                Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
+                AutoSizeAxes = Axes.Y,
+                Width = 500f,
+                Direction = FillDirection.Vertical,
+                Children = new Drawable[]
+                {
+                    p = new DrawableMultiplayerRoom(new MultiplayerRoom
+                    {
+                        Name = @"Great Room Right Here",
+                        Host = new User { Username = @"Naeferith", Country = new Country { FlagName = @"FR" }},
+                        Status = MultiplayerRoomStatus.Open,
+                        CurrentBeatmap = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" },
+                    }),
+                    new DrawableMultiplayerRoom(new MultiplayerRoom
+                    {
+                        Name = @"Relax It's The Weekend",
+                        Host = new User{ Username = @"Someone", Country = new Country { FlagName = @"CA" }},
+                        Status = MultiplayerRoomStatus.Playing,
+                        CurrentBeatmap = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" },
+                    }),
+                }
+            });
+
+            AddStep(@"change state", () => { p.Room.Status = MultiplayerRoomStatus.Playing; });
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
deleted file mode 100644
index a6dbcda8f9..0000000000
--- a/osu.Desktop.VisualTests/Tests/TestCaseMultiRoomPanel.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using OpenTK;
-using OpenTK.Graphics;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Screens.Select;
-using osu.Game.Screens.Multiplayer;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Primitives;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input;
-using osu.Game.Graphics.Sprites;
-using osu.Framework.Testing;
-
-namespace osu.Desktop.VisualTests.Tests
-{
-    class TestCaseMultiRoomPanel : TestCase
-    {
-        private MultiRoomPanel test;
-        private FillFlowContainer panelContainer;
-
-        public override string Description => @"Select your favourite room";
-
-        private void action(int action)
-        {
-            switch (action)
-            {
-                case 0:
-                    test.State = test.State == MultiRoomPanel.PanelState.Free ? MultiRoomPanel.PanelState.Busy : MultiRoomPanel.PanelState.Free;
-                    break;
-            }
-        }
-
-        public override void Reset()
-        {
-            base.Reset();
-
-            AddStep(@"ChangeState", () => action(0));
-
-            Add(panelContainer = new FillFlowContainer //Positionning container
-            {
-                RelativeSizeAxes = Axes.Both,
-                Anchor = Anchor.Centre,
-                Origin = Anchor.Centre,
-                Direction = FillDirection.Vertical,
-                Size = new Vector2(0.4f, 0.5f),
-                Children = new Drawable[]
-                {
-                    test = new MultiRoomPanel("Great Room Right Here", "Naeferith", 0),
-                    new MultiRoomPanel("Relax it's the weekend", "Someone", 1),
-                }
-            });
-        }
-        protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
-        {
-            foreach (MultiRoomPanel panel in panelContainer.Children)
-            {
-                panel.BorderThickness = 0;
-                if (panel.Clicked == true)
-                {
-                    panel.BorderThickness = 3;
-                    panel.Clicked = false;
-                }
-            }
-            return base.OnMouseUp(state, args);
-        }
-    }
-}
diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
index 2374b07bf3..fa507527aa 100644
--- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
+++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
@@ -220,7 +220,7 @@
     <Compile Include="Tests\TestCaseLeaderboard.cs" />
     <Compile Include="Beatmaps\TestWorkingBeatmap.cs" />
     <Compile Include="Tests\TestCaseBeatmapDetailArea.cs" />
-    <Compile Include="Tests\TestCaseMultiRoomPanel.cs" />
+    <Compile Include="Tests\TestCaseDrawableMultiplayerRoom.cs" />
   </ItemGroup>
   <ItemGroup />
   <ItemGroup />
diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
new file mode 100644
index 0000000000..5c9e72bcf2
--- /dev/null
+++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
@@ -0,0 +1,26 @@
+// 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.ComponentModel;
+using osu.Game.Database;
+using osu.Game.Users;
+
+namespace osu.Game.Online.Multiplayer
+{
+    public class MultiplayerRoom
+    {
+        public string Name { get; set; }
+        public User Host { get; set; }
+        public MultiplayerRoomStatus Status { get; set; }
+        public BeatmapMetadata CurrentBeatmap { get; set; }
+    }
+
+    public enum MultiplayerRoomStatus
+    {
+        [Description(@"Welcoming Players")]
+        Open,
+
+        [Description(@"Now Playing")]
+        Playing,
+    }
+}
diff --git a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs b/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
new file mode 100644
index 0000000000..2c0e7db1db
--- /dev/null
+++ b/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
@@ -0,0 +1,215 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Users;
+
+namespace osu.Game.Screens.Multiplayer
+{
+    public class DrawableMultiplayerRoom : ClickableContainer
+    {
+        private const float content_padding = 5;
+        private const float height = 90;
+
+        private readonly Box sideStrip;
+        private readonly OsuSpriteText status;
+        private readonly OsuSpriteText host;
+        private readonly OsuSpriteText rankBounds;
+        private readonly FillFlowContainer<OsuSpriteText> beatmapInfoFlow;
+        private readonly OsuSpriteText beatmapTitle;
+        private readonly OsuSpriteText beatmapArtist;
+
+        private Color4 openColour;
+        private Color4 playingColour;
+
+        public readonly MultiplayerRoom Room;
+
+        public DrawableMultiplayerRoom(MultiplayerRoom room)
+        {
+            Room = room;
+
+            RelativeSizeAxes = Axes.X;
+            Height = height;
+            CornerRadius = 5;
+            Masking = true;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Colour = Color4.Black.Opacity(40),
+                Radius = 5,
+            };
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.Gray(34),
+                },
+                new Background(@"Backgrounds/bg4")
+                {
+                    RelativeSizeAxes = Axes.Both,
+                },
+                sideStrip = new Box
+                {
+                    RelativeSizeAxes = Axes.Y,
+                    Width = 5,
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = content_padding, Bottom = content_padding * 2, Left = Height + content_padding * 2, Right = content_padding },
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Direction = FillDirection.Vertical,
+                            Spacing = new Vector2(5f),
+                            Children = new Drawable[]
+                            {
+                                new OsuSpriteText
+                                {
+                                    Text = Room.Name,
+                                    TextSize = 18,
+                                },
+                                new Container
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    Height = 20f,
+                                    Children = new Drawable[]
+                                    {
+                                        new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.X,
+                                            RelativeSizeAxes = Axes.Y,
+                                            Direction = FillDirection.Horizontal,
+                                            Spacing = new Vector2(5f, 0f),
+                                            Children = new Drawable[]
+                                            {
+                                                new DrawableFlag(Room.Host?.Country?.FlagName ?? "__")
+                                                {
+                                                    Width = 30f,
+                                                    RelativeSizeAxes = Axes.Y,
+                                                },
+                                                new Container
+                                                {
+                                                    Width = 40f,
+                                                    RelativeSizeAxes = Axes.Y,
+                                                },
+                                                new OsuSpriteText
+                                                {
+                                                    Text = "hosted by",
+                                                    Anchor = Anchor.CentreLeft,
+                                                    Origin = Anchor.CentreLeft,
+                                                    TextSize = 14,
+                                                },
+                                                host = new OsuSpriteText
+                                                {
+                                                    Anchor = Anchor.CentreLeft,
+                                                    Origin = Anchor.CentreLeft,
+                                                    Text = Room.Host?.Username ?? @"",
+                                                    TextSize = 14,
+                                                    Font = @"Exo2.0-BoldItalic",
+                                                },
+                                            },
+                                        },
+                                        rankBounds = new OsuSpriteText
+                                        {
+                                            Anchor = Anchor.CentreRight,
+                                            Origin = Anchor.CentreRight,
+                                            Text = "#6895 - #50024",
+                                            TextSize = 14,
+                                            Margin = new MarginPadding { Right = 10 },
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.BottomLeft,
+                            Origin = Anchor.BottomLeft,
+                            RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Direction = FillDirection.Vertical,
+                            Children = new Drawable[]
+                            {
+                                status = new OsuSpriteText
+                                {
+                                    TextSize = 14,
+                                    Font = @"Exo2.0-Bold",
+                                },
+                                beatmapInfoFlow = new FillFlowContainer<OsuSpriteText>
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Direction = FillDirection.Horizontal,
+                                    Children = new[]
+                                    {
+                                        beatmapTitle = new OsuSpriteText
+                                        {
+                                            TextSize = 14,
+                                            Font = @"Exo2.0-BoldItalic",
+                                        },
+                                        new OsuSpriteText
+                                        {
+                                            Text = @" - ",
+                                            TextSize = 14,
+                                            Font = @"Exo2.0-RegularItalic",
+                                        },
+                                        beatmapArtist = new OsuSpriteText
+                                        {
+                                            TextSize = 14,
+                                            Font = @"Exo2.0-RegularItalic",
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours, LocalisationEngine localisation)
+        {
+            openColour = colours.GreenLight;
+            playingColour = colours.Purple;
+            beatmapInfoFlow.Colour = rankBounds.Colour = colours.Gray9;
+            host.Colour = colours.Blue;
+
+            if (Room.CurrentBeatmap != null)
+            {
+                beatmapTitle.Current = localisation.GetUnicodePreference(Room.CurrentBeatmap.TitleUnicode, Room.CurrentBeatmap.Title);
+                beatmapArtist.Current = localisation.GetUnicodePreference(Room.CurrentBeatmap.ArtistUnicode, Room.CurrentBeatmap.Artist);
+            }
+
+            updateStatus();
+        }
+
+        private void updateStatus()
+        {
+            if (Room == null) return;
+
+            status.Text = Room.Status.GetDescription();
+
+            foreach (Drawable d in new Drawable[] { sideStrip, status })
+                d.FadeColour(Room.Status == MultiplayerRoomStatus.Playing? playingColour : openColour);
+        }
+    }
+}
diff --git a/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs b/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
deleted file mode 100644
index cbc4cc1182..0000000000
--- a/osu.Game/Screens/Multiplayer/MultiRoomPanel.cs
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using OpenTK;
-using OpenTK.Graphics;
-using osu.Framework;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Primitives;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input;
-using osu.Game.Graphics.Backgrounds;
-using osu.Game.Graphics.Sprites;
-
-namespace osu.Game.Screens.Multiplayer
-{
-    public class MultiRoomPanel : ClickableContainer, IStateful<MultiRoomPanel.PanelState>
-    {
-        private PanelState state;
-        public PanelState State
-        {
-            get { return state; }
-            set
-            {
-                if (state == value)
-                    return;
-
-                state = value;
-                switch (state)
-                {
-                    case PanelState.Free:
-                        statusColour = ColourFree;
-                        statusString = "Welcoming Players";
-                        UpdatePanel(this);
-                        break;
-
-                    case PanelState.Busy:
-                        statusColour = ColourBusy;
-                        statusString = "Now Playing";
-                        UpdatePanel(this);
-                        break;
-                }
-            }
-        }
-
-        private bool didClick;
-        private string roomName;
-        private string hostName;
-
-        private int roomStatus;
-        private Color4 statusColour;
-        private string statusString;
-
-        private Box sideSprite;
-        private OsuSpriteText hostSprite;
-        private OsuSpriteText statusSprite;
-        private OsuSpriteText roomSprite;
-
-        public const int PANEL_HEIGHT = 90;
-        public const int CONTENT_PADDING = 5;
-
-        public Color4 ColourFree = new Color4(166, 204, 0, 255);
-        public Color4 ColourBusy = new Color4(135, 102, 237, 255);
-
-        public bool Clicked
-        {
-            get { return didClick; }
-            set
-            {
-                didClick = value;
-            }
-        }
-
-        public void UpdatePanel(MultiRoomPanel panel)
-        {
-            panel.BorderColour = statusColour;
-            panel.sideSprite.Colour = statusColour;
-
-            statusSprite.Colour = statusColour;
-            statusSprite.Text = statusString;
-        }
-
-        public MultiRoomPanel(string matchName = "Room Name", string host = "Undefined", int status = 0)
-        {
-            roomName = matchName;
-            hostName = host;
-            roomStatus = status;
-
-            if (status == 0)
-            {
-                statusColour = ColourFree;
-                statusString = "Welcoming Players";
-            }
-            else
-            {
-                statusColour = ColourBusy;
-                statusString = "Now Playing";
-            }
-
-            RelativeSizeAxes = Axes.X;
-            Height = PANEL_HEIGHT;
-            Masking = true;
-            CornerRadius = 5;
-            BorderThickness = 0;
-            BorderColour = statusColour;
-            EdgeEffect = new EdgeEffect
-            {
-                Type = EdgeEffectType.Shadow,
-                Colour = Color4.Black.Opacity(40),
-                Radius = 5,
-            };
-            Children = new Drawable[]
-            {
-                new Box
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Colour = new Color4(34,34,34, 255),
-                },
-                sideSprite = new Box
-                {
-                    RelativeSizeAxes = Axes.Y,
-                    Width = 5,
-                    Colour = statusColour,
-                },
-                /*new Box //Beatmap img 
-                {
-
-                },*/
-                new Background(@"Backgrounds/bg4")
-                {
-                    RelativeSizeAxes = Axes.Both,
-                }
-                ,
-                new FillFlowContainer
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Anchor = Anchor.TopRight,
-                    Origin = Anchor.TopRight,
-                    Direction = FillDirection.Vertical,
-                    Size = new Vector2(0.75f,1),
-
-                    Children = new Drawable[]
-                    {
-                        roomSprite = new OsuSpriteText
-                        {
-                            Text = roomName,
-                            TextSize = 18,
-                            Margin = new MarginPadding { Top = CONTENT_PADDING },
-                        },
-                        new FillFlowContainer
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            Height = 20,
-                            Direction = FillDirection.Horizontal,
-                            Spacing = new Vector2(5,0),
-                            Children = new Drawable[]
-                            {
-                                
-                                
-                                new Container
-                                {
-                                    Masking = true,
-                                    CornerRadius = 5,
-                                    Width = 30,
-                                    Height = 20,
-                                    Children = new Drawable[]
-                                    {
-                                        new Box
-                                        {
-                                            RelativeSizeAxes = Axes.Both,
-                                            Colour = Color4.Gray,
-                                        }
-                                    }
-                                    
-                                },
-                                new Container
-                                {
-                                    Masking = true,
-                                    CornerRadius = 5,
-                                    Width = 40,
-                                    Height = 20,
-                                    Children = new Drawable[]
-                                    {
-                                        new Box
-                                        {
-                                            RelativeSizeAxes = Axes.Both,
-                                            Colour = new Color4(173,56,126,255),
-                                        }
-                                    }
-                                },
-                                new OsuSpriteText
-                                {
-                                    Text = "hosted by",
-                                    Anchor = Anchor.CentreLeft,
-                                    Origin = Anchor.CentreLeft,
-                                    TextSize = 14,
-                                },
-                                hostSprite = new OsuSpriteText
-                                {
-                                    Text = hostName,
-                                    Font = @"Exo2.0-Bold",
-                                    Anchor = Anchor.CentreLeft,
-                                    Origin = Anchor.CentreLeft,
-                                    Shear = new Vector2(0.1f,0),
-                                    Colour = new Color4(69,179,222,255),
-                                    TextSize = 14,
-                                },
-                                new OsuSpriteText
-                                {
-                                    Text = "#6895 - #50024",
-                                    Anchor = Anchor.CentreLeft,
-                                    Origin = Anchor.CentreLeft,
-                                    TextSize = 14,
-                                    Margin = new MarginPadding { Left = 80 },
-                                    Colour = new Color4(153,153,153,255),
-                                }
-                            }
-                        },
-                        statusSprite = new OsuSpriteText
-                        {
-                            Text = statusString,
-                            TextSize = 14,
-                            Font = @"Exo2.0-Bold",
-                            Colour = statusColour,
-                            Margin = new MarginPadding { Top = 10 }
-                        },
-                        new FillFlowContainer
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            Direction = FillDirection.Horizontal,
-                            Children = new Drawable[]
-                            {
-                                new OsuSpriteText
-                                {
-                                    Text = "Platina",
-                                    Font = @"Exo2.0-Bold",
-                                    TextSize = 14,
-                                    Shear = new Vector2(0.1f,0),
-                                    Colour = new Color4(153,153,153,255),
-                                },
-                                new OsuSpriteText
-                                {
-                                    Text = " - " + "Maaya Sakamoto",
-                                    TextSize = 14,
-                                    Shear = new Vector2(0.1f,0),
-                                    Colour = new Color4(153,153,153,255),
-                                }
-                            }
-                        },
-                    }
-                },
-            };
-        }
-
-        protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
-        {
-            BorderThickness = 3;
-            didClick = true;
-            return base.OnMouseUp(state, args);
-        }
-
-        public enum PanelState
-        {
-            Free,
-            Busy
-        }
-    }
-}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index f96bbaa08a..d773e0c093 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -428,7 +428,8 @@
     <Compile Include="Overlays\Music\PlaylistOverlay.cs" />
     <Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
     <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
-    <Compile Include="Screens\Multiplayer\MultiRoomPanel.cs" />
+    <Compile Include="Screens\Multiplayer\DrawableMultiplayerRoom.cs" />
+    <Compile Include="Online\Multiplayer\MultiplayerRoom.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
@@ -451,6 +452,9 @@
   <ItemGroup />
   <ItemGroup />
   <ItemGroup />
+  <ItemGroup>
+    <Folder Include="Online\Multiplayer\" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
        Other similar extension points exist, see Microsoft.Common.targets.

From 82fdf1a918cb126a4b1f9fb6840d0f8b0b5ef7e9 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 01:01:55 -0300
Subject: [PATCH 060/179] Bindables, avatars, more cleanup

---
 .../Tests/TestCaseDrawableMultiplayerRoom.cs  | 34 ++++++++-
 .../Multiplayer/DrawableMultiplayerRoom.cs    | 73 +++++++++++++------
 2 files changed, 81 insertions(+), 26 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
index c9a0dfa280..a358fd6701 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
@@ -40,21 +40,49 @@ namespace osu.Desktop.VisualTests.Tests
                     p = new DrawableMultiplayerRoom(new MultiplayerRoom
                     {
                         Name = @"Great Room Right Here",
-                        Host = new User { Username = @"Naeferith", Country = new Country { FlagName = @"FR" }},
+                        Host = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }},
                         Status = MultiplayerRoomStatus.Open,
                         CurrentBeatmap = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" },
                     }),
                     new DrawableMultiplayerRoom(new MultiplayerRoom
                     {
                         Name = @"Relax It's The Weekend",
-                        Host = new User{ Username = @"Someone", Country = new Country { FlagName = @"CA" }},
+                        Host = new User{ Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }},
                         Status = MultiplayerRoomStatus.Playing,
                         CurrentBeatmap = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" },
                     }),
                 }
             });
 
-            AddStep(@"change state", () => { p.Room.Status = MultiplayerRoomStatus.Playing; });
+            AddStep(@"change state", () =>
+            {
+                p.Room.Value.Status = MultiplayerRoomStatus.Playing;
+                p.Room.TriggerChange();
+            });
+
+            AddStep(@"change name", () =>
+            {
+                p.Room.Value.Name = @"I Changed Name";
+                p.Room.TriggerChange();
+            });
+
+            AddStep(@"change host", () =>
+            {
+                p.Room.Value.Host = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } };
+                p.Room.TriggerChange();
+            });
+
+            AddStep(@"change beatmap", () =>
+            {
+                p.Room.Value.CurrentBeatmap = null;
+                p.Room.TriggerChange();
+            });
+
+            AddStep(@"change state", () =>
+            {
+                p.Room.Value.Status = MultiplayerRoomStatus.Open;
+                p.Room.TriggerChange();
+            });
         }
     }
 }
diff --git a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs b/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
index 2c0e7db1db..4a746226d8 100644
--- a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
@@ -4,6 +4,7 @@
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
+using osu.Framework.Configuration;
 using osu.Framework.Extensions;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
@@ -24,21 +25,26 @@ namespace osu.Game.Screens.Multiplayer
         private const float height = 90;
 
         private readonly Box sideStrip;
-        private readonly OsuSpriteText status;
+        private readonly UpdateableAvatar avatar;
+        private readonly OsuSpriteText name;
+        private readonly Container flagContainer;
         private readonly OsuSpriteText host;
         private readonly OsuSpriteText rankBounds;
+        private readonly OsuSpriteText status;
         private readonly FillFlowContainer<OsuSpriteText> beatmapInfoFlow;
         private readonly OsuSpriteText beatmapTitle;
+        private readonly OsuSpriteText beatmapDash;
         private readonly OsuSpriteText beatmapArtist;
 
         private Color4 openColour;
         private Color4 playingColour;
+        private LocalisationEngine localisation;
 
-        public readonly MultiplayerRoom Room;
+        public readonly Bindable<MultiplayerRoom> Room;
 
         public DrawableMultiplayerRoom(MultiplayerRoom room)
         {
-            Room = room;
+            Room = new Bindable<MultiplayerRoom>(room);
 
             RelativeSizeAxes = Axes.X;
             Height = height;
@@ -65,12 +71,19 @@ namespace osu.Game.Screens.Multiplayer
                 sideStrip = new Box
                 {
                     RelativeSizeAxes = Axes.Y,
-                    Width = 5,
+                    Width = content_padding,
+                },
+                avatar = new UpdateableAvatar
+                {
+                    Size = new Vector2(Height - content_padding* 2),
+                    Masking = true,
+                    CornerRadius = 5f,
+                    Margin = new MarginPadding { Left = content_padding * 2, Top = content_padding },
                 },
                 new Container
                 {
                     RelativeSizeAxes = Axes.Both,
-                    Padding = new MarginPadding { Top = content_padding, Bottom = content_padding * 2, Left = Height + content_padding * 2, Right = content_padding },
+                    Padding = new MarginPadding { Top = content_padding, Bottom = content_padding, Left = Height + content_padding * 2, Right = content_padding },
                     Children = new Drawable[]
                     {
                         new FillFlowContainer
@@ -81,9 +94,8 @@ namespace osu.Game.Screens.Multiplayer
                             Spacing = new Vector2(5f),
                             Children = new Drawable[]
                             {
-                                new OsuSpriteText
+                                name = new OsuSpriteText
                                 {
-                                    Text = Room.Name,
                                     TextSize = 18,
                                 },
                                 new Container
@@ -100,7 +112,7 @@ namespace osu.Game.Screens.Multiplayer
                                             Spacing = new Vector2(5f, 0f),
                                             Children = new Drawable[]
                                             {
-                                                new DrawableFlag(Room.Host?.Country?.FlagName ?? "__")
+                                                flagContainer = new Container
                                                 {
                                                     Width = 30f,
                                                     RelativeSizeAxes = Axes.Y,
@@ -121,7 +133,6 @@ namespace osu.Game.Screens.Multiplayer
                                                 {
                                                     Anchor = Anchor.CentreLeft,
                                                     Origin = Anchor.CentreLeft,
-                                                    Text = Room.Host?.Username ?? @"",
                                                     TextSize = 14,
                                                     Font = @"Exo2.0-BoldItalic",
                                                 },
@@ -131,7 +142,7 @@ namespace osu.Game.Screens.Multiplayer
                                         {
                                             Anchor = Anchor.CentreRight,
                                             Origin = Anchor.CentreRight,
-                                            Text = "#6895 - #50024",
+                                            Text = "#0 - #0",
                                             TextSize = 14,
                                             Margin = new MarginPadding { Right = 10 },
                                         },
@@ -146,6 +157,7 @@ namespace osu.Game.Screens.Multiplayer
                             RelativeSizeAxes = Axes.X,
                             AutoSizeAxes = Axes.Y,
                             Direction = FillDirection.Vertical,
+                            Margin = new MarginPadding { Bottom = content_padding },
                             Children = new Drawable[]
                             {
                                 status = new OsuSpriteText
@@ -165,9 +177,8 @@ namespace osu.Game.Screens.Multiplayer
                                             TextSize = 14,
                                             Font = @"Exo2.0-BoldItalic",
                                         },
-                                        new OsuSpriteText
+                                        beatmapDash = new OsuSpriteText
                                         {
-                                            Text = @" - ",
                                             TextSize = 14,
                                             Font = @"Exo2.0-RegularItalic",
                                         },
@@ -183,33 +194,49 @@ namespace osu.Game.Screens.Multiplayer
                     },
                 },
             };
+
+            Room.ValueChanged += displayRoom;
         }
 
         [BackgroundDependencyLoader]
         private void load(OsuColour colours, LocalisationEngine localisation)
         {
+            this.localisation = localisation;
+
             openColour = colours.GreenLight;
             playingColour = colours.Purple;
             beatmapInfoFlow.Colour = rankBounds.Colour = colours.Gray9;
             host.Colour = colours.Blue;
 
-            if (Room.CurrentBeatmap != null)
-            {
-                beatmapTitle.Current = localisation.GetUnicodePreference(Room.CurrentBeatmap.TitleUnicode, Room.CurrentBeatmap.Title);
-                beatmapArtist.Current = localisation.GetUnicodePreference(Room.CurrentBeatmap.ArtistUnicode, Room.CurrentBeatmap.Artist);
-            }
-
-            updateStatus();
+            Room.TriggerChange();
         }
 
-        private void updateStatus()
+        private void displayRoom(MultiplayerRoom room)
         {
-            if (Room == null) return;
+            name.Text = room.Name;
+            status.Text = room.Status.GetDescription();
+            host.Text = room.Host.Username;
+            flagContainer.Children = new[] { new DrawableFlag(room.Host.Country?.FlagName ?? @"__") { RelativeSizeAxes = Axes.Both } };
+            avatar.User = room.Host;
 
-            status.Text = Room.Status.GetDescription();
+            if (room.CurrentBeatmap != null)
+            {
+                beatmapTitle.Current = localisation.GetUnicodePreference(room.CurrentBeatmap.TitleUnicode, room.CurrentBeatmap.Title);
+                beatmapDash.Text = @" - ";
+                beatmapArtist.Current = localisation.GetUnicodePreference(room.CurrentBeatmap.ArtistUnicode, room.CurrentBeatmap.Artist);
+            }
+            else
+            {
+                beatmapTitle.Current = null;
+                beatmapArtist.Current = null;
+
+                beatmapTitle.Text = @"Changing map";
+                beatmapDash.Text = string.Empty;
+                beatmapArtist.Text = string.Empty;
+            }
 
             foreach (Drawable d in new Drawable[] { sideStrip, status })
-                d.FadeColour(Room.Status == MultiplayerRoomStatus.Playing? playingColour : openColour);
+                d.FadeColour(room.Status == MultiplayerRoomStatus.Playing? playingColour : openColour, 100);
         }
     }
 }

From 0c30b2c698cdec323febd733b49ca220f5d5f4a4 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 01:07:26 -0300
Subject: [PATCH 061/179] Remove folder from osu.Game.csproj

---
 osu.Game/osu.Game.csproj | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 78c6eb52a3..2b0a6435bc 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -449,9 +449,6 @@
   <ItemGroup />
   <ItemGroup />
   <ItemGroup />
-  <ItemGroup>
-    <Folder Include="Online\Multiplayer\" />
-  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
        Other similar extension points exist, see Microsoft.Common.targets.
@@ -460,4 +457,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
\ No newline at end of file
+</Project>

From 800a31947014ee6bb9c77a95eb782f6ac4f057d6 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 01:08:35 -0300
Subject: [PATCH 062/179] Remove newline

---
 osu.Game/osu.Game.csproj | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 2b0a6435bc..27bcb8fa39 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -457,4 +457,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
+</Project>
\ No newline at end of file

From 26bf9dd64bfdceb7df4bf162ee21ee519a0947ec Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 01:13:51 -0300
Subject: [PATCH 063/179] Remove unused Background

---
 osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs b/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
index 4a746226d8..97efef2968 100644
--- a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
@@ -64,10 +64,6 @@ namespace osu.Game.Screens.Multiplayer
                     RelativeSizeAxes = Axes.Both,
                     Colour = OsuColour.Gray(34),
                 },
-                new Background(@"Backgrounds/bg4")
-                {
-                    RelativeSizeAxes = Axes.Both,
-                },
                 sideStrip = new Box
                 {
                     RelativeSizeAxes = Axes.Y,

From d1df89584437c5d3cb4af3c3424d62485506fe00 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 01:16:59 -0300
Subject: [PATCH 064/179] Unused using

---
 osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs b/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
index 97efef2968..37182cb692 100644
--- a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Localisation;
 using osu.Game.Graphics;
-using osu.Game.Graphics.Backgrounds;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Users;

From 87cdf5aac61fcf3a609823b556344218be1798cb Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 01:29:39 -0300
Subject: [PATCH 065/179] CI fixes

---
 .../Tests/TestCaseDrawableMultiplayerRoom.cs       | 14 +++-----------
 1 file changed, 3 insertions(+), 11 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
index a358fd6701..1c6c0d6420 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
@@ -1,25 +1,17 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using OpenTK;
-using OpenTK.Graphics;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Screens.Select;
-using osu.Game.Screens.Multiplayer;
-using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Primitives;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input;
-using osu.Game.Graphics.Sprites;
+using osu.Framework.Graphics;
 using osu.Framework.Testing;
+using osu.Game.Screens.Multiplayer;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Users;
 using osu.Game.Database;
 
 namespace osu.Desktop.VisualTests.Tests
 {
-    class TestCaseDrawableMultiplayerRoom : TestCase
+    internal class TestCaseDrawableMultiplayerRoom : TestCase
     {
         public override string Description => @"Select your favourite room";
 

From 059af0850b42ec57851194dffc226f1c9a844967 Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Sun, 21 May 2017 23:47:08 -0500
Subject: [PATCH 066/179] Changes in-line with framework

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index 079b3c92c4..76db6d1a06 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Sprites;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Timing;
 using System;
+using osu.Framework.Audio.Track;
 
 namespace osu.Game.Screens.Menu
 {
@@ -23,8 +24,8 @@ namespace osu.Game.Screens.Menu
         private Box leftBox;
         private Box rightBox;
 
-        private const int amplitude_dead_zone = 9000;
-        private const float alpha_multiplier = (short.MaxValue - amplitude_dead_zone) / 0.55f;
+        private const float amplitude_dead_zone = 0.25f;
+        private const float alpha_multiplier = (1 - amplitude_dead_zone) / 0.55f;
         private const int box_max_alpha = 200;
         private const double box_fade_in_time = 65;
 
@@ -68,12 +69,13 @@ namespace osu.Game.Screens.Menu
             }
             else if (newBeat >= 0)
             {
-                short[] lev = beatmap.Value.Track.ChannelPeakAmplitudes;
+
+                TrackAmplitudes amp = beatmap.Value.Track.PeakAmplitudes;
                 bool nextIsLeft = newBeat % 2 == 0;
                 if (kiai ? nextIsLeft : newBeat % (int)timeSignature == 0)
                 {
                     leftBox.ClearTransforms();
-                    leftBox.FadeTo(Math.Max(0, (lev[0] - amplitude_dead_zone) / alpha_multiplier), 65);
+                    leftBox.FadeTo(Math.Max(0, (amp.LeftChannel - amplitude_dead_zone) / alpha_multiplier), 65);
                     using (leftBox.BeginDelayedSequence(box_fade_in_time))
                         leftBox.FadeOut(beatLength, EasingTypes.In);
                     leftBox.DelayReset();
@@ -81,7 +83,7 @@ namespace osu.Game.Screens.Menu
                 if (kiai ? !nextIsLeft : newBeat % (int)timeSignature == 0)
                 {
                     rightBox.ClearTransforms();
-                    rightBox.FadeTo(Math.Max(0, (lev[1] - amplitude_dead_zone) / alpha_multiplier), 65);
+                    rightBox.FadeTo(Math.Max(0, (amp.LeftChannel - amplitude_dead_zone) / alpha_multiplier), 65);
                     using (rightBox.BeginDelayedSequence(box_fade_in_time))
                         rightBox.FadeOut(beatLength, EasingTypes.In);
                     rightBox.DelayReset();

From 6bf0ca59fe2f0b19b454e7c750a1bed89e6ccabc Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 02:03:26 -0300
Subject: [PATCH 067/179] Make FilterControl not scroll with the panels

---
 osu.Game/Overlays/Direct/FilterControl.cs |  4 +++-
 osu.Game/Overlays/DirectOverlay.cs        | 23 +++++++++++------------
 2 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index b35938f3a6..bdb880d5cc 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -21,6 +21,8 @@ namespace osu.Game.Overlays.Direct
 {
     public class FilterControl : Container
     {
+        public static readonly float HEIGHT = 35 + 32 + 30 + padding * 2; // search + mode toggle buttons + sort tabs + padding
+
         /// <summary>
         /// The height of the content below the filter control (tab strip + result count text).
         /// </summary>
@@ -50,7 +52,7 @@ namespace osu.Game.Overlays.Direct
         public FilterControl()
         {
             RelativeSizeAxes = Axes.X;
-            Height = 35 + 32 + 30 + padding * 2; // search + mode toggle buttons + sort tabs + padding
+            Height = HEIGHT;
             DisplayStyle.Value = PanelDisplayStyle.Grid;
 
             Children = new Drawable[]
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index c56d995c8a..284c13299c 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -77,24 +77,18 @@ namespace osu.Game.Overlays
                         },
                     },
                 },
-                new ScrollContainer
+                new Container
                 {
                     RelativeSizeAxes = Axes.Both,
-                    ScrollDraggerVisible = false,
-                    Padding = new MarginPadding { Top = Header.HEIGHT },
-                    Children = new Drawable[]
+                    Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
+                    Children = new[]
                     {
-                        new ReverseDepthFillFlowContainer<Drawable>
+                        new ScrollContainer
                         {
-                            RelativeSizeAxes = Axes.X,
-                            AutoSizeAxes = Axes.Y,
-                            Direction = FillDirection.Vertical,
+                            RelativeSizeAxes = Axes.Both,
+                            ScrollDraggerVisible = false,
                             Children = new Drawable[]
                             {
-                                filter = new FilterControl
-                                {
-                                    RelativeSizeAxes = Axes.X,
-                                },
                                 panels = new FillFlowContainer<DirectPanel>
                                 {
                                     RelativeSizeAxes = Axes.X,
@@ -106,6 +100,11 @@ namespace osu.Game.Overlays
                         },
                     },
                 },
+                filter = new FilterControl
+                {
+                    RelativeSizeAxes = Axes.X,
+                    Margin = new MarginPadding { Top = Header.HEIGHT },
+                },
                 header = new Header
                 {
                     RelativeSizeAxes = Axes.X,

From 3c35badf069d7e0d62a1fca6802b4905df6ce898 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 02:03:53 -0300
Subject: [PATCH 068/179] Unused using

---
 osu.Game/Overlays/DirectOverlay.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 284c13299c..928ab3b300 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -11,7 +11,6 @@ using osu.Framework.Input;
 using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
-using osu.Game.Graphics.Containers;
 using osu.Game.Overlays.Direct;
 
 namespace osu.Game.Overlays

From 7ce3b73ecdaf0f1df0d390f51c24a5318a0c4cf0 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 03:11:42 -0300
Subject: [PATCH 069/179] Added UserPanel and UserStatus

---
 .../Tests/TestCaseUserPanel.cs                |  40 ++++
 .../osu.Desktop.VisualTests.csproj            |   1 +
 osu.Game/Users/UserPanel.cs                   | 183 ++++++++++++++++++
 osu.Game/Users/UserStatus.cs                  |  55 ++++++
 osu.Game/osu.Game.csproj                      |   2 +
 5 files changed, 281 insertions(+)
 create mode 100644 osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
 create mode 100644 osu.Game/Users/UserPanel.cs
 create mode 100644 osu.Game/Users/UserStatus.cs

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
new file mode 100644
index 0000000000..ab8bb425a7
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
@@ -0,0 +1,40 @@
+// 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.Testing;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Users;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+	internal class TestCaseUserPanel : TestCase
+	{
+		public override string Description => @"Panels for displaying a user's status";
+        
+        public override void Reset()
+        {
+            base.Reset();
+
+            UserPanel p;
+            Add(p = new UserPanel(new User
+            {
+                Username = @"flyte",
+                Id = 3103765,
+                Country = new Country { FlagName = @"JP" },
+                CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3103765/5b012e13611d5761caa7e24fecb3d3a16e1cf48fc2a3032cfd43dd444af83d82.jpeg"
+            })
+            {
+                Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
+                Width = 300,
+            });
+
+            p.Status.Value = new UserStatusOnline();
+
+            AddStep(@"spectating", () => { p.Status.Value = new UserStatusSpectating(); });
+            AddStep(@"multiplaying", () => { p.Status.Value = new UserStatusMultiplayerGame(); });
+            AddStep(@"modding", () => { p.Status.Value = new UserStatusModding(); });
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
index dbb1750b72..e68935a561 100644
--- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
+++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
@@ -220,6 +220,7 @@
     <Compile Include="Tests\TestCaseLeaderboard.cs" />
     <Compile Include="Beatmaps\TestWorkingBeatmap.cs" />
     <Compile Include="Tests\TestCaseBeatmapDetailArea.cs" />
+    <Compile Include="Tests\TestCaseUserPanel.cs" />
   </ItemGroup>
   <ItemGroup />
   <ItemGroup />
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
new file mode 100644
index 0000000000..80d706d985
--- /dev/null
+++ b/osu.Game/Users/UserPanel.cs
@@ -0,0 +1,183 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Users
+{
+    public class UserPanel : Container
+    {
+        private const float height = 100;
+        private const float content_padding = 10;
+        private const float status_height = 30;
+
+        private readonly User user;
+        private OsuColour colours;
+
+        private readonly Container cover;
+        private readonly Box statusBg;
+        private readonly OsuSpriteText statusMessage;
+
+        public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>();
+
+        public UserPanel(User user)
+        {
+            this.user = user;
+
+            Height = height;
+            Masking = true;
+            CornerRadius = 5;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Colour = Color4.Black.Opacity(0.25f),
+                Radius = 4,
+            };
+
+            Children = new Drawable[]
+            {
+                cover = new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                },
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black.Opacity(0.7f),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = content_padding, Bottom = status_height + content_padding, Left = content_padding, Right = content_padding },
+                    Children = new Drawable[]
+                    {
+                        new UpdateableAvatar
+                        {
+                            Size = new Vector2(height - status_height - content_padding * 2),
+                            User = user,
+                            Masking = true,
+                            CornerRadius = 5,
+                            EdgeEffect = new EdgeEffect
+                            {
+                                Type = EdgeEffectType.Shadow,
+                                Colour = Color4.Black.Opacity(0.25f),
+                                Radius = 4,
+                            },
+                        },
+                        new Container
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Margin = new MarginPadding { Left = height - status_height - content_padding },
+                            Children = new Drawable[]
+                            {
+                                new OsuSpriteText
+                                {
+                                    Text = user.Username,
+                                    TextSize = 18,
+                                    Font = @"Exo2.0-SemiBoldItalic",
+                                },
+                                new FillFlowContainer
+                                {
+                                    Anchor = Anchor.BottomLeft,
+                                    Origin = Anchor.BottomLeft,
+                                    AutoSizeAxes = Axes.X,
+                                    Height = 20f,
+                                    Direction = FillDirection.Horizontal,
+                                    Spacing = new Vector2(5f, 0f),
+                                    Children = new Drawable[]
+                                    {
+                                        new DrawableFlag(user.Country?.FlagName ?? @"__")
+                                        {
+                                            Width = 30f,
+                                            RelativeSizeAxes = Axes.Y,
+                                        },
+                                        new Container
+                                        {
+                                            Width = 40f,
+                                            RelativeSizeAxes = Axes.Y,
+                                        },
+                                        new CircularContainer
+                                        {
+                                            Width = 20f,
+                                            RelativeSizeAxes = Axes.Y,
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+                new Container
+                {
+                    Anchor = Anchor.BottomLeft,
+                    Origin = Anchor.BottomLeft,
+                    RelativeSizeAxes = Axes.X,
+                    Height = status_height,
+                    Children = new Drawable[]
+                    {
+                        statusBg = new Box
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Alpha = 0.5f,
+                        },
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
+                            AutoSizeAxes = Axes.Both,
+                            Spacing = new Vector2(5f, 0f),
+                            Children = new[]
+                            {
+                                new TextAwesome
+                                {
+                                    Anchor = Anchor.CentreLeft,
+                                    Origin = Anchor.CentreLeft,
+                                    Icon = FontAwesome.fa_circle_o,
+                                    Shadow = true,
+                                    TextSize = 14,
+                                },
+                                statusMessage = new OsuSpriteText
+                                {
+                                    Anchor = Anchor.CentreLeft,
+                                    Origin = Anchor.CentreLeft,
+                                    Font = @"Exo2.0-Semibold",
+                                },
+                            },
+                        },
+                    },
+                },
+            };
+
+            Status.ValueChanged += displayStatus;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours, TextureStore textures)
+        {
+            this.colours = colours;
+
+            cover.Add(new AsyncLoadWrapper(new Sprite
+            {
+                Texture = textures.Get(user.CoverUrl),
+                FillMode = FillMode.Fill,
+                OnLoadComplete = d => d.FadeInFromZero(200),
+            }) { RelativeSizeAxes = Axes.Both });
+        }
+
+        private void displayStatus(UserStatus status)
+        {
+            statusBg.FadeColour(status.Colour(colours), 200);
+            statusMessage.Text = status.Message;
+        }
+    }
+}
diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs
new file mode 100644
index 0000000000..b6ae53c9cd
--- /dev/null
+++ b/osu.Game/Users/UserStatus.cs
@@ -0,0 +1,55 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
+using osu.Game.Graphics;
+
+namespace osu.Game.Users
+{
+    public abstract class UserStatus
+    {
+        public abstract string Message { get; }
+        public abstract Color4 Colour(OsuColour colours);
+    }
+
+    public abstract class UserStatusAvailable : UserStatus
+    {
+        public override Color4 Colour(OsuColour colours) => colours.BlueDarker;
+    }
+
+    public abstract class UserStatusBusy : UserStatus
+    {
+        public override Color4 Colour(OsuColour colours) => colours.YellowDark;
+    }
+
+    public class UserStatusOnline : UserStatusAvailable
+    {
+        public override string Message => @"Online";
+    }
+
+    public class UserStatusSpectating : UserStatusAvailable
+    {
+    	public override string Message => @"Spectating a game";
+    }
+
+    public class UserStatusInLobby : UserStatusAvailable
+    {
+    	public override string Message => @"in Multiplayer Lobby";
+    }
+
+    public class UserStatusSoloGame :  UserStatusBusy
+    {
+    	public override string Message => @"Solo Game";
+    }
+
+    public class UserStatusMultiplayerGame: UserStatusBusy
+    {
+    	public override string Message => @"Multiplaying";
+    }
+
+    public class UserStatusModding : UserStatus
+    {
+    	public override string Message => @"Modding a map";
+        public override Color4 Colour(OsuColour colours) => colours.PurpleDark;
+    }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1631311ef6..ba72b0422d 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -425,6 +425,8 @@
     <Compile Include="Overlays\Music\PlaylistOverlay.cs" />
     <Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
     <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
+    <Compile Include="Users\UserPanel.cs" />
+    <Compile Include="Users\UserStatus.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

From 77affc1eb71a67153c2259ccb4a01bfb993e69b7 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 03:13:55 -0300
Subject: [PATCH 070/179] Split onto multiple lines

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs | 8 +++++++-
 osu.Game/Overlays/Direct/FilterControl.cs   | 6 +++++-
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index fc63045e15..63298052c3 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -110,7 +110,13 @@ namespace osu.Game.Overlays.Direct
                                     RelativeSizeAxes = Axes.X,
                                     AutoSizeAxes = Axes.Y,
                                     Direction = FillDirection.Vertical,
-                                    Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = horizontal_padding },
+                                    Padding = new MarginPadding
+                                    {
+                                        Top = vertical_padding,
+                                        Bottom = vertical_padding,
+                                        Left = horizontal_padding,
+                                        Right = horizontal_padding,
+                                    },
                                     Children = new Drawable[]
                                     {
                                         new FillFlowContainer
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index bdb880d5cc..185ab7c321 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -44,7 +44,11 @@ namespace osu.Game.Overlays.Direct
         public ResultCounts ResultCounts
         {
             get { return resultCounts; }
-            set { resultCounts = value; updateResultCounts(); }
+            set
+            {
+                resultCounts = value;
+                updateResultCounts();
+            }
         }
 
         protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || RankStatusDropdown.Contains(screenSpacePos);

From bdab545ca44779bc080d868f064e3f67306f15f0 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 03:15:22 -0300
Subject: [PATCH 071/179] Use BeatmapBackgroundSprite

---
 osu.Game/Overlays/Direct/DirectPanel.cs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 9aae719d5c..8a56cf392e 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -36,10 +36,9 @@ namespace osu.Game.Overlays.Direct
 
         protected Drawable GetBackground(TextureStore textures)
         {
-            return new AsyncLoadWrapper(new Sprite
+            return new AsyncLoadWrapper(new BeatmapBackgroundSprite(new OnlineWorkingBeatmap(SetInfo.Beatmaps.FirstOrDefault(), textures, null))
             {
                 FillMode = FillMode.Fill,
-                Texture = new OnlineWorkingBeatmap(SetInfo.Beatmaps.FirstOrDefault(), textures, null).Background,
                 OnLoadComplete = d => d.FadeInFromZero(400, EasingTypes.Out),
             }) { RelativeSizeAxes = Axes.Both };
         }

From a73cf929946cb30ec7154014b66fe091595c6bdc Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 03:27:08 -0300
Subject: [PATCH 072/179] Add second UserPanel to visual test, center cover
 sprite

---
 .../Tests/TestCaseUserPanel.cs                | 42 +++++++++++++------
 osu.Game/Users/UserPanel.cs                   |  3 ++
 2 files changed, 32 insertions(+), 13 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
index ab8bb425a7..efee8e0e24 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
@@ -5,6 +5,8 @@ using osu.Framework.Testing;
 using osu.Framework.Graphics;
 using osu.Game.Graphics.UserInterface;
 using osu.Game.Users;
+using osu.Framework.Graphics.Containers;
+using OpenTK;
 
 namespace osu.Desktop.VisualTests.Tests
 {
@@ -16,25 +18,39 @@ namespace osu.Desktop.VisualTests.Tests
         {
             base.Reset();
 
-            UserPanel p;
-            Add(p = new UserPanel(new User
-            {
-                Username = @"flyte",
-                Id = 3103765,
-                Country = new Country { FlagName = @"JP" },
-                CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3103765/5b012e13611d5761caa7e24fecb3d3a16e1cf48fc2a3032cfd43dd444af83d82.jpeg"
-            })
+            UserPanel flyte;
+            UserPanel peppy;
+            Add(new FillFlowContainer
             {
                 Anchor = Anchor.Centre,
                 Origin = Anchor.Centre,
-                Width = 300,
+                AutoSizeAxes = Axes.Both,
+                Spacing = new Vector2(10f),
+                Children = new[]
+                {
+                    flyte = new UserPanel(new User
+                    {
+                        Username = @"flyte",
+                        Id = 3103765,
+                        Country = new Country { FlagName = @"JP" },
+                        CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3103765/5b012e13611d5761caa7e24fecb3d3a16e1cf48fc2a3032cfd43dd444af83d82.jpeg"
+                    }),
+                    peppy = new UserPanel(new User
+                    {
+                        Username = @"peppy",
+                        Id = 2,
+                        Country = new Country { FlagName = @"AU" },
+                        CoverUrl = @"https://assets.ppy.sh/user-profile-covers/2/08cad88747c235a64fca5f1b770e100f120827ded1ffe3b66bfcd19c940afa65.jpeg"
+                    }),
+                },
             });
 
-            p.Status.Value = new UserStatusOnline();
+            flyte.Status.Value = new UserStatusOnline();
+            peppy.Status.Value = new UserStatusSoloGame();
 
-            AddStep(@"spectating", () => { p.Status.Value = new UserStatusSpectating(); });
-            AddStep(@"multiplaying", () => { p.Status.Value = new UserStatusMultiplayerGame(); });
-            AddStep(@"modding", () => { p.Status.Value = new UserStatusModding(); });
+            AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); });
+            AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
+            AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
         }
     }
 }
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 80d706d985..4de309fb62 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -34,6 +34,7 @@ namespace osu.Game.Users
         {
             this.user = user;
 
+            Width = 300;
             Height = height;
             Masking = true;
             CornerRadius = 5;
@@ -168,6 +169,8 @@ namespace osu.Game.Users
 
             cover.Add(new AsyncLoadWrapper(new Sprite
             {
+                Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
                 Texture = textures.Get(user.CoverUrl),
                 FillMode = FillMode.Fill,
                 OnLoadComplete = d => d.FadeInFromZero(200),

From aa40f882586b9a9137e9b0a1aa0c1cffaa9aad8f Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 03:32:02 -0300
Subject: [PATCH 073/179] Add UserStatusOffline

---
 osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs | 1 +
 osu.Game/Users/UserStatus.cs                       | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
index efee8e0e24..d783d42f1f 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
@@ -51,6 +51,7 @@ namespace osu.Desktop.VisualTests.Tests
             AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); });
             AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
             AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
+            AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); });
         }
     }
 }
diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs
index b6ae53c9cd..97ee47423e 100644
--- a/osu.Game/Users/UserStatus.cs
+++ b/osu.Game/Users/UserStatus.cs
@@ -22,6 +22,12 @@ namespace osu.Game.Users
         public override Color4 Colour(OsuColour colours) => colours.YellowDark;
     }
 
+    public class UserStatusOffline : UserStatus
+    {
+    	public override string Message => @"Offline";
+        public override Color4 Colour(OsuColour colours) => colours.Gray7;
+    }
+
     public class UserStatusOnline : UserStatusAvailable
     {
         public override string Message => @"Online";

From 8a364c606824d7376286f38429dfe5545db3454e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 03:37:36 -0300
Subject: [PATCH 074/179] CI fixes

---
 osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs |  8 ++++----
 osu.Game/Users/UserStatus.cs                       | 12 ++++++------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
index d783d42f1f..f7f0f8b24a 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
@@ -10,10 +10,10 @@ using OpenTK;
 
 namespace osu.Desktop.VisualTests.Tests
 {
-	internal class TestCaseUserPanel : TestCase
-	{
-		public override string Description => @"Panels for displaying a user's status";
-        
+    internal class TestCaseUserPanel : TestCase
+    {
+        public override string Description => @"Panels for displaying a user's status";
+
         public override void Reset()
         {
             base.Reset();
diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs
index 97ee47423e..0ff8333c24 100644
--- a/osu.Game/Users/UserStatus.cs
+++ b/osu.Game/Users/UserStatus.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Users
 
     public class UserStatusOffline : UserStatus
     {
-    	public override string Message => @"Offline";
+        public override string Message => @"Offline";
         public override Color4 Colour(OsuColour colours) => colours.Gray7;
     }
 
@@ -35,27 +35,27 @@ namespace osu.Game.Users
 
     public class UserStatusSpectating : UserStatusAvailable
     {
-    	public override string Message => @"Spectating a game";
+        public override string Message => @"Spectating a game";
     }
 
     public class UserStatusInLobby : UserStatusAvailable
     {
-    	public override string Message => @"in Multiplayer Lobby";
+        public override string Message => @"in Multiplayer Lobby";
     }
 
     public class UserStatusSoloGame :  UserStatusBusy
     {
-    	public override string Message => @"Solo Game";
+        public override string Message => @"Solo Game";
     }
 
     public class UserStatusMultiplayerGame: UserStatusBusy
     {
-    	public override string Message => @"Multiplaying";
+        public override string Message => @"Multiplaying";
     }
 
     public class UserStatusModding : UserStatus
     {
-    	public override string Message => @"Modding a map";
+        public override string Message => @"Modding a map";
         public override Color4 Colour(OsuColour colours) => colours.PurpleDark;
     }
 }

From 62ca76bc4188f5241fa222b7e4ac971c449b9c3f Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 03:42:32 -0300
Subject: [PATCH 075/179] Unused using

---
 osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
index f7f0f8b24a..7c2745ee0f 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
@@ -3,7 +3,6 @@
 
 using osu.Framework.Testing;
 using osu.Framework.Graphics;
-using osu.Game.Graphics.UserInterface;
 using osu.Game.Users;
 using osu.Framework.Graphics.Containers;
 using OpenTK;

From a1547f12d426c1139afc749d75bdef963026c86a Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Mon, 22 May 2017 04:38:21 -0500
Subject: [PATCH 076/179] Applied suggestions + Update Framework

---
 osu-framework                            |  2 +-
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 50 +++++++++++-------------
 osu.Game/osu.Game.csproj                 |  1 +
 3 files changed, 24 insertions(+), 29 deletions(-)

diff --git a/osu-framework b/osu-framework
index 42e26d49b9..9967572490 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 42e26d49b9046fcb96c123b0dfb48e06d741e162
+Subproject commit 9967572490ed2d1fa3c746311753c0cf00c08607
diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index 76db6d1a06..4968da9689 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -4,6 +4,7 @@
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Colour;
 using osu.Game.Graphics.Containers;
@@ -11,7 +12,7 @@ using osu.Framework.Graphics.Sprites;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Timing;
 using System;
-using osu.Framework.Audio.Track;
+using TrackAmplitudes = osu.Framework.Audio.Track.Track.TrackAmplitudes;
 
 namespace osu.Game.Screens.Menu
 {
@@ -19,13 +20,14 @@ namespace osu.Game.Screens.Menu
     {
         public override bool HandleInput => false;
 
-        private Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
+        private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
 
         private Box leftBox;
         private Box rightBox;
 
         private const float amplitude_dead_zone = 0.25f;
         private const float alpha_multiplier = (1 - amplitude_dead_zone) / 0.55f;
+        private const float kiai_multiplier = (1 - (amplitude_dead_zone * 0.9f)) / 0.8f;
         private const int box_max_alpha = 200;
         private const double box_fade_in_time = 65;
 
@@ -45,7 +47,7 @@ namespace osu.Game.Screens.Menu
                     Width = 300,
                     Alpha = 0,
                     BlendingMode = BlendingMode.Additive,
-                    ColourInfo = ColourInfo.GradientHorizontal(new Color4(255, 255, 255, box_max_alpha), Color4.Transparent),
+                    ColourInfo = ColourInfo.GradientHorizontal(new Color4(255, 255, 255, box_max_alpha), Color4.Black.Opacity(0)),
                 },
                 rightBox = new Box
                 {
@@ -55,46 +57,38 @@ namespace osu.Game.Screens.Menu
                     Width = 300,
                     Alpha = 0,
                     BlendingMode = BlendingMode.Additive,
-                    ColourInfo = ColourInfo.GradientHorizontal(Color4.Transparent, new Color4(255, 255, 255, box_max_alpha)),
+                    ColourInfo = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), new Color4(255, 255, 255, box_max_alpha)),
                 }
             };
         }
 
         protected override void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
         {
-            if (!beatmap?.Value?.Track?.IsRunning ?? false)
+            if (newBeat < 0)
+                return;
+            TrackAmplitudes amp = beatmap.Value.Track.CurrentAmplitudes;
+            if (newBeat % (kiai ? 2 : (int)timeSignature) == 0)
             {
-                leftBox.FadeOut(50);
-                rightBox.FadeOut(50);
+                leftBox.ClearTransforms();
+                leftBox.FadeTo(Math.Max(0, (amp.LeftChannel - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), 65);
+                using (leftBox.BeginDelayedSequence(box_fade_in_time))
+                    leftBox.FadeOut(beatLength, EasingTypes.In);
+                leftBox.DelayReset();
             }
-            else if (newBeat >= 0)
+            if (kiai ? newBeat % 2 == 1 : newBeat % (int)timeSignature == 0)
             {
-
-                TrackAmplitudes amp = beatmap.Value.Track.PeakAmplitudes;
-                bool nextIsLeft = newBeat % 2 == 0;
-                if (kiai ? nextIsLeft : newBeat % (int)timeSignature == 0)
-                {
-                    leftBox.ClearTransforms();
-                    leftBox.FadeTo(Math.Max(0, (amp.LeftChannel - amplitude_dead_zone) / alpha_multiplier), 65);
-                    using (leftBox.BeginDelayedSequence(box_fade_in_time))
-                        leftBox.FadeOut(beatLength, EasingTypes.In);
-                    leftBox.DelayReset();
-                }
-                if (kiai ? !nextIsLeft : newBeat % (int)timeSignature == 0)
-                {
-                    rightBox.ClearTransforms();
-                    rightBox.FadeTo(Math.Max(0, (amp.LeftChannel - amplitude_dead_zone) / alpha_multiplier), 65);
-                    using (rightBox.BeginDelayedSequence(box_fade_in_time))
-                        rightBox.FadeOut(beatLength, EasingTypes.In);
-                    rightBox.DelayReset();
-                }
+                rightBox.ClearTransforms();
+                rightBox.FadeTo(Math.Max(0, (amp.RightChannel - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), 65);
+                using (rightBox.BeginDelayedSequence(box_fade_in_time))
+                    rightBox.FadeOut(beatLength, EasingTypes.In);
+                rightBox.DelayReset();
             }
         }
 
         [BackgroundDependencyLoader]
         private void load(OsuGameBase game)
         {
-            beatmap = game.Beatmap;
+            beatmap.BindTo(game.Beatmap);
         }
     }
 }
\ No newline at end of file
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index b94c19e1f8..a5e57f10db 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -183,6 +183,7 @@
     <Compile Include="Database\RulesetDatabase.cs" />
     <Compile Include="Rulesets\Scoring\Score.cs" />
     <Compile Include="Rulesets\Scoring\ScoreProcessor.cs" />
+    <Compile Include="Screens\Menu\MenuSideFlashes.cs" />
     <Compile Include="Screens\Play\HUD\HealthDisplay.cs" />
     <Compile Include="Screens\Play\HUDOverlay.cs" />
     <Compile Include="Screens\Play\HUD\StandardHealthDisplay.cs" />

From f2b5be27c8581da525b50179638e4282eb28491c Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Mon, 22 May 2017 04:53:32 -0500
Subject: [PATCH 077/179] CI Fixes

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index 4968da9689..b961cd9adb 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -22,12 +22,12 @@ namespace osu.Game.Screens.Menu
 
         private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
 
-        private Box leftBox;
-        private Box rightBox;
+        private readonly Box leftBox;
+        private readonly Box rightBox;
 
         private const float amplitude_dead_zone = 0.25f;
         private const float alpha_multiplier = (1 - amplitude_dead_zone) / 0.55f;
-        private const float kiai_multiplier = (1 - (amplitude_dead_zone * 0.9f)) / 0.8f;
+        private const float kiai_multiplier = (1 - amplitude_dead_zone * 0.95f) / 0.8f;
         private const int box_max_alpha = 200;
         private const double box_fade_in_time = 65;
 

From 601b840713df654f2dd6855e2156c04673e0f999 Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Mon, 22 May 2017 04:55:33 -0500
Subject: [PATCH 078/179] Apply suggestions

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index b961cd9adb..7e2b6b988a 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -66,8 +66,9 @@ namespace osu.Game.Screens.Menu
         {
             if (newBeat < 0)
                 return;
+
             TrackAmplitudes amp = beatmap.Value.Track.CurrentAmplitudes;
-            if (newBeat % (kiai ? 2 : (int)timeSignature) == 0)
+            if (kiai ? newBeat % 2 == 0 : newBeat % (int)timeSignature) == 0)
             {
                 leftBox.ClearTransforms();
                 leftBox.FadeTo(Math.Max(0, (amp.LeftChannel - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), 65);
@@ -75,6 +76,7 @@ namespace osu.Game.Screens.Menu
                     leftBox.FadeOut(beatLength, EasingTypes.In);
                 leftBox.DelayReset();
             }
+
             if (kiai ? newBeat % 2 == 1 : newBeat % (int)timeSignature == 0)
             {
                 rightBox.ClearTransforms();

From 63196df541280dee0e60b498e03dd44dd0c8a5f4 Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Mon, 22 May 2017 05:05:54 -0500
Subject: [PATCH 079/179] Typo fix Forgot the pharenteses

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index 7e2b6b988a..aee501461a 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -68,7 +68,7 @@ namespace osu.Game.Screens.Menu
                 return;
 
             TrackAmplitudes amp = beatmap.Value.Track.CurrentAmplitudes;
-            if (kiai ? newBeat % 2 == 0 : newBeat % (int)timeSignature) == 0)
+            if (kiai ? newBeat % 2 == 0 : newBeat % (int)timeSignature == 0)
             {
                 leftBox.ClearTransforms();
                 leftBox.FadeTo(Math.Max(0, (amp.LeftChannel - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), 65);

From 6cef3021c7f0a3e5fcc8694adc40e08b149c6b86 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 19:50:01 +0900
Subject: [PATCH 080/179] Adjust sizing to better fit glows within the
 playfield.

---
 .../Objects/Drawables/Pieces/CirclePiece.cs                   | 2 +-
 osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs             | 4 ++--
 osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs                  | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index 9f91488fe3..6d3a9e79f6 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
             {
                 Type = EdgeEffectType.Glow,
                 Colour = AccentColour,
-                Radius = KiaiMode ? 50 : 8
+                Radius = KiaiMode ? 40 : 8
             };
         }
     }
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 6a6353fde2..816c5042ce 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
         /// <summary>
         /// Diameter of a circle relative to the size of the <see cref="TaikoPlayfield"/>.
         /// </summary>
-        public const float PLAYFIELD_RELATIVE_DIAMETER = 0.5f;
+        public const float PLAYFIELD_RELATIVE_DIAMETER = 0.45f;
 
         /// <summary>
         /// Scale multiplier for a strong circle.
         /// </summary>
-        public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.5f;
+        public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.4f;
 
         /// <summary>
         /// Default circle diameter.
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 2278158506..d1d895fc1d 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         /// <summary>
         /// The default play field height.
         /// </summary>
-        public const float DEFAULT_PLAYFIELD_HEIGHT = 168f;
+        public const float DEFAULT_PLAYFIELD_HEIGHT = 178f;
 
         /// <summary>
         /// The offset from <see cref="left_area_size"/> which the center of the hit target lies at.
@@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         /// <summary>
         /// This is a very special type of container. It serves a similar purpose to <see cref="FillMode.Fit"/>, however unlike <see cref="FillMode.Fit"/>,
         /// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent.
-        /// 
+        ///
         /// <para>
         /// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable
         /// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this

From 9235cbff8d4c543413bac65e5e53b12ef19f7cf9 Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Mon, 22 May 2017 05:59:16 -0500
Subject: [PATCH 081/179] Apply suggestions

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 44 +++++++++++++-----------
 1 file changed, 24 insertions(+), 20 deletions(-)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index aee501461a..61d3ef10a8 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -22,6 +22,9 @@ namespace osu.Game.Screens.Menu
 
         private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
 
+        private static readonly ColourInfo gradient_white_to_transparent_black = ColourInfo.GradientHorizontal(new Color4(255, 255, 255, box_max_alpha), Color4.Black.Opacity(0));
+        private static readonly ColourInfo gradient_transparent_black_to_white = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), new Color4(255, 255, 255, box_max_alpha));
+
         private readonly Box leftBox;
         private readonly Box rightBox;
 
@@ -30,6 +33,7 @@ namespace osu.Game.Screens.Menu
         private const float kiai_multiplier = (1 - amplitude_dead_zone * 0.95f) / 0.8f;
         private const int box_max_alpha = 200;
         private const double box_fade_in_time = 65;
+        private const int box_width = 300;
 
         public MenuSideFlashes()
         {
@@ -44,47 +48,47 @@ namespace osu.Game.Screens.Menu
                     Anchor = Anchor.CentreLeft,
                     Origin = Anchor.CentreLeft,
                     RelativeSizeAxes = Axes.Y,
-                    Width = 300,
+                    Width = box_width,
                     Alpha = 0,
                     BlendingMode = BlendingMode.Additive,
-                    ColourInfo = ColourInfo.GradientHorizontal(new Color4(255, 255, 255, box_max_alpha), Color4.Black.Opacity(0)),
+                    ColourInfo = gradient_white_to_transparent_black,
                 },
                 rightBox = new Box
                 {
                     Anchor = Anchor.CentreRight,
                     Origin = Anchor.CentreRight,
                     RelativeSizeAxes = Axes.Y,
-                    Width = 300,
+                    Width = box_width,
                     Alpha = 0,
                     BlendingMode = BlendingMode.Additive,
-                    ColourInfo = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), new Color4(255, 255, 255, box_max_alpha)),
+                    ColourInfo = gradient_transparent_black_to_white,
                 }
             };
         }
 
+        private bool kiai;
+        private double beatLength;
+
         protected override void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
         {
             if (newBeat < 0)
                 return;
 
-            TrackAmplitudes amp = beatmap.Value.Track.CurrentAmplitudes;
-            if (kiai ? newBeat % 2 == 0 : newBeat % (int)timeSignature == 0)
-            {
-                leftBox.ClearTransforms();
-                leftBox.FadeTo(Math.Max(0, (amp.LeftChannel - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), 65);
-                using (leftBox.BeginDelayedSequence(box_fade_in_time))
-                    leftBox.FadeOut(beatLength, EasingTypes.In);
-                leftBox.DelayReset();
-            }
+            this.kiai = kiai;
+            this.beatLength = beatLength;
 
+            if (kiai ? newBeat % 2 == 0 : newBeat % (int)timeSignature == 0)
+                flash(leftBox);
             if (kiai ? newBeat % 2 == 1 : newBeat % (int)timeSignature == 0)
-            {
-                rightBox.ClearTransforms();
-                rightBox.FadeTo(Math.Max(0, (amp.RightChannel - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), 65);
-                using (rightBox.BeginDelayedSequence(box_fade_in_time))
-                    rightBox.FadeOut(beatLength, EasingTypes.In);
-                rightBox.DelayReset();
-            }
+                flash(rightBox);
+        }
+
+        private void flash(Drawable d)
+        {
+            TrackAmplitudes amp = beatmap.Value.Track.CurrentAmplitudes;
+            d.FadeTo(Math.Max(0, ((d.Equals(leftBox) ? amp.LeftChannel : amp.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time);
+            using (d.BeginDelayedSequence(box_fade_in_time))
+                d.FadeOut(beatLength, EasingTypes.In);
         }
 
         [BackgroundDependencyLoader]

From 35814e47e46bdd0df6f98d43b91a72e58a96eb91 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 12:37:19 -0300
Subject: [PATCH 082/179] Colour -> GetAppropriateColour, adjust status change
 transition

---
 osu.Game/Users/UserPanel.cs  |  4 ++--
 osu.Game/Users/UserStatus.cs | 14 +++++++-------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 4de309fb62..745fd166f8 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Users
                         new Container
                         {
                             RelativeSizeAxes = Axes.Both,
-                            Margin = new MarginPadding { Left = height - status_height - content_padding },
+                            Padding = new MarginPadding { Left = height - status_height - content_padding },
                             Children = new Drawable[]
                             {
                                 new OsuSpriteText
@@ -179,7 +179,7 @@ namespace osu.Game.Users
 
         private void displayStatus(UserStatus status)
         {
-            statusBg.FadeColour(status.Colour(colours), 200);
+            statusBg.FadeColour(status.GetAppropriateColour(colours), 500, EasingTypes.OutQuint);
             statusMessage.Text = status.Message;
         }
     }
diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs
index 0ff8333c24..dcb5ccbd8f 100644
--- a/osu.Game/Users/UserStatus.cs
+++ b/osu.Game/Users/UserStatus.cs
@@ -9,23 +9,23 @@ namespace osu.Game.Users
     public abstract class UserStatus
     {
         public abstract string Message { get; }
-        public abstract Color4 Colour(OsuColour colours);
+        public abstract Color4 GetAppropriateColour(OsuColour colours);
     }
 
     public abstract class UserStatusAvailable : UserStatus
     {
-        public override Color4 Colour(OsuColour colours) => colours.BlueDarker;
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.BlueDarker;
     }
 
     public abstract class UserStatusBusy : UserStatus
     {
-        public override Color4 Colour(OsuColour colours) => colours.YellowDark;
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDark;
     }
 
-    public class UserStatusOffline : UserStatus
-    {
+    public class UserStatusOffline : UserStatus
+    {
         public override string Message => @"Offline";
-        public override Color4 Colour(OsuColour colours) => colours.Gray7;
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray7;
     }
 
     public class UserStatusOnline : UserStatusAvailable
@@ -56,6 +56,6 @@ namespace osu.Game.Users
     public class UserStatusModding : UserStatus
     {
         public override string Message => @"Modding a map";
-        public override Color4 Colour(OsuColour colours) => colours.PurpleDark;
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
     }
 }

From 261ea4c1767c85931e5f7ebef1062fe746fd7f31 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 12:41:59 -0300
Subject: [PATCH 083/179] CoverBackgroundSprite for actually async cover
 loading

---
 osu.Game/Users/UserPanel.cs | 36 ++++++++++++++++++++++++++----------
 1 file changed, 26 insertions(+), 10 deletions(-)

diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 745fd166f8..81e28e2735 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -159,22 +159,21 @@ namespace osu.Game.Users
                 },
             };
 
+            cover.Add(new AsyncLoadWrapper(new CoverBackgroundSprite(user)
+            {
+            	Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
+                FillMode = FillMode.Fill,
+                OnLoadComplete = d => d.FadeInFromZero(200),
+            }) { RelativeSizeAxes = Axes.Both });
+
             Status.ValueChanged += displayStatus;
         }
 
         [BackgroundDependencyLoader]
-        private void load(OsuColour colours, TextureStore textures)
+        private void load(OsuColour colours)
         {
             this.colours = colours;
-
-            cover.Add(new AsyncLoadWrapper(new Sprite
-            {
-                Anchor = Anchor.Centre,
-                Origin = Anchor.Centre,
-                Texture = textures.Get(user.CoverUrl),
-                FillMode = FillMode.Fill,
-                OnLoadComplete = d => d.FadeInFromZero(200),
-            }) { RelativeSizeAxes = Axes.Both });
         }
 
         private void displayStatus(UserStatus status)
@@ -182,5 +181,22 @@ namespace osu.Game.Users
             statusBg.FadeColour(status.GetAppropriateColour(colours), 500, EasingTypes.OutQuint);
             statusMessage.Text = status.Message;
         }
+
+        private class CoverBackgroundSprite : Sprite
+        {
+            private readonly User user;
+
+            public CoverBackgroundSprite(User user)
+            {
+                this.user = user;
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(TextureStore textures)
+            {
+                if (!string.IsNullOrEmpty(user.CoverUrl))
+                    Texture = textures.Get(user.CoverUrl);
+            }
+        }
     }
 }

From 03f6cded8463a75cf762d8661c13e840ee349ccc Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 12:44:58 -0300
Subject: [PATCH 084/179] MultiplayerRoom -> Room

---
 ...wableMultiplayerRoom.cs => TestCaseDrawableRoom.cs} |  8 ++++----
 osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj |  2 +-
 .../Online/Multiplayer/{MultiplayerRoom.cs => Room.cs} |  2 +-
 .../{DrawableMultiplayerRoom.cs => DrawableRoom.cs}    | 10 +++++-----
 osu.Game/osu.Game.csproj                               |  4 ++--
 5 files changed, 13 insertions(+), 13 deletions(-)
 rename osu.Desktop.VisualTests/Tests/{TestCaseDrawableMultiplayerRoom.cs => TestCaseDrawableRoom.cs} (89%)
 rename osu.Game/Online/Multiplayer/{MultiplayerRoom.cs => Room.cs} (91%)
 rename osu.Game/Screens/Multiplayer/{DrawableMultiplayerRoom.cs => DrawableRoom.cs} (95%)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
similarity index 89%
rename from osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
rename to osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
index 1c6c0d6420..8150de9fb8 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDrawableMultiplayerRoom.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
@@ -11,7 +11,7 @@ using osu.Game.Database;
 
 namespace osu.Desktop.VisualTests.Tests
 {
-    internal class TestCaseDrawableMultiplayerRoom : TestCase
+    internal class TestCaseDrawableRoom : TestCase
     {
         public override string Description => @"Select your favourite room";
 
@@ -19,7 +19,7 @@ namespace osu.Desktop.VisualTests.Tests
         {
             base.Reset();
 
-            DrawableMultiplayerRoom p;
+            DrawableRoom p;
             Add(new FillFlowContainer
             {
                 Anchor = Anchor.Centre,
@@ -29,14 +29,14 @@ namespace osu.Desktop.VisualTests.Tests
                 Direction = FillDirection.Vertical,
                 Children = new Drawable[]
                 {
-                    p = new DrawableMultiplayerRoom(new MultiplayerRoom
+                    p = new DrawableRoom(new Room
                     {
                         Name = @"Great Room Right Here",
                         Host = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }},
                         Status = MultiplayerRoomStatus.Open,
                         CurrentBeatmap = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" },
                     }),
-                    new DrawableMultiplayerRoom(new MultiplayerRoom
+                    new DrawableRoom(new Room
                     {
                         Name = @"Relax It's The Weekend",
                         Host = new User{ Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }},
diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
index fa507527aa..6e6f0a691d 100644
--- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
+++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
@@ -220,7 +220,7 @@
     <Compile Include="Tests\TestCaseLeaderboard.cs" />
     <Compile Include="Beatmaps\TestWorkingBeatmap.cs" />
     <Compile Include="Tests\TestCaseBeatmapDetailArea.cs" />
-    <Compile Include="Tests\TestCaseDrawableMultiplayerRoom.cs" />
+    <Compile Include="Tests\TestCaseDrawableRoom.cs" />
   </ItemGroup>
   <ItemGroup />
   <ItemGroup />
diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/Room.cs
similarity index 91%
rename from osu.Game/Online/Multiplayer/MultiplayerRoom.cs
rename to osu.Game/Online/Multiplayer/Room.cs
index 5c9e72bcf2..9def99386d 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -7,7 +7,7 @@ using osu.Game.Users;
 
 namespace osu.Game.Online.Multiplayer
 {
-    public class MultiplayerRoom
+    public class Room
     {
         public string Name { get; set; }
         public User Host { get; set; }
diff --git a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
similarity index 95%
rename from osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
rename to osu.Game/Screens/Multiplayer/DrawableRoom.cs
index 37182cb692..4a7622b888 100644
--- a/osu.Game/Screens/Multiplayer/DrawableMultiplayerRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -18,7 +18,7 @@ using osu.Game.Users;
 
 namespace osu.Game.Screens.Multiplayer
 {
-    public class DrawableMultiplayerRoom : ClickableContainer
+    public class DrawableRoom : ClickableContainer
     {
         private const float content_padding = 5;
         private const float height = 90;
@@ -39,11 +39,11 @@ namespace osu.Game.Screens.Multiplayer
         private Color4 playingColour;
         private LocalisationEngine localisation;
 
-        public readonly Bindable<MultiplayerRoom> Room;
+        public readonly Bindable<Room> Room;
 
-        public DrawableMultiplayerRoom(MultiplayerRoom room)
+        public DrawableRoom(Room room)
         {
-            Room = new Bindable<MultiplayerRoom>(room);
+            Room = new Bindable<Room>(room);
 
             RelativeSizeAxes = Axes.X;
             Height = height;
@@ -206,7 +206,7 @@ namespace osu.Game.Screens.Multiplayer
             Room.TriggerChange();
         }
 
-        private void displayRoom(MultiplayerRoom room)
+        private void displayRoom(Room room)
         {
             name.Text = room.Name;
             status.Text = room.Status.GetDescription();
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 27bcb8fa39..7af7bdf1e6 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -425,8 +425,8 @@
     <Compile Include="Overlays\Music\PlaylistOverlay.cs" />
     <Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
     <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
-    <Compile Include="Screens\Multiplayer\DrawableMultiplayerRoom.cs" />
-    <Compile Include="Online\Multiplayer\MultiplayerRoom.cs" />
+    <Compile Include="Screens\Multiplayer\DrawableRoom.cs" />
+    <Compile Include="Online\Multiplayer\Room.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

From 2c16d9c3a7531d7397117719a0cd6e8e5b04befd Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 12:45:40 -0300
Subject: [PATCH 085/179] CurrentBeatmap -> Beatmap

---
 osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs | 6 +++---
 osu.Game/Online/Multiplayer/Room.cs                   | 2 +-
 osu.Game/Screens/Multiplayer/DrawableRoom.cs          | 6 +++---
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
index 8150de9fb8..2a10be8906 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
@@ -34,14 +34,14 @@ namespace osu.Desktop.VisualTests.Tests
                         Name = @"Great Room Right Here",
                         Host = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }},
                         Status = MultiplayerRoomStatus.Open,
-                        CurrentBeatmap = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" },
+                        Beatmap = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" },
                     }),
                     new DrawableRoom(new Room
                     {
                         Name = @"Relax It's The Weekend",
                         Host = new User{ Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }},
                         Status = MultiplayerRoomStatus.Playing,
-                        CurrentBeatmap = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" },
+                        Beatmap = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" },
                     }),
                 }
             });
@@ -66,7 +66,7 @@ namespace osu.Desktop.VisualTests.Tests
 
             AddStep(@"change beatmap", () =>
             {
-                p.Room.Value.CurrentBeatmap = null;
+                p.Room.Value.Beatmap = null;
                 p.Room.TriggerChange();
             });
 
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
index 9def99386d..56ac71f6f9 100644
--- a/osu.Game/Online/Multiplayer/Room.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Online.Multiplayer
         public string Name { get; set; }
         public User Host { get; set; }
         public MultiplayerRoomStatus Status { get; set; }
-        public BeatmapMetadata CurrentBeatmap { get; set; }
+        public BeatmapMetadata Beatmap { get; set; }
     }
 
     public enum MultiplayerRoomStatus
diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index 4a7622b888..b33100c900 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -214,11 +214,11 @@ namespace osu.Game.Screens.Multiplayer
             flagContainer.Children = new[] { new DrawableFlag(room.Host.Country?.FlagName ?? @"__") { RelativeSizeAxes = Axes.Both } };
             avatar.User = room.Host;
 
-            if (room.CurrentBeatmap != null)
+            if (room.Beatmap != null)
             {
-                beatmapTitle.Current = localisation.GetUnicodePreference(room.CurrentBeatmap.TitleUnicode, room.CurrentBeatmap.Title);
+                beatmapTitle.Current = localisation.GetUnicodePreference(room.Beatmap.TitleUnicode, room.Beatmap.Title);
                 beatmapDash.Text = @" - ";
-                beatmapArtist.Current = localisation.GetUnicodePreference(room.CurrentBeatmap.ArtistUnicode, room.CurrentBeatmap.Artist);
+                beatmapArtist.Current = localisation.GetUnicodePreference(room.Beatmap.ArtistUnicode, room.Beatmap.Artist);
             }
             else
             {

From 65df2d2b70d262691a01cf424c74ebe95b1c4841 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 12:46:41 -0300
Subject: [PATCH 086/179] MultiplayerRoomStatus -> RoomStatus

---
 osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs | 8 ++++----
 osu.Game/Online/Multiplayer/Room.cs                   | 4 ++--
 osu.Game/Screens/Multiplayer/DrawableRoom.cs          | 2 +-
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
index 2a10be8906..08034ee4ae 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
@@ -33,14 +33,14 @@ namespace osu.Desktop.VisualTests.Tests
                     {
                         Name = @"Great Room Right Here",
                         Host = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }},
-                        Status = MultiplayerRoomStatus.Open,
+                        Status = RoomStatus.Open,
                         Beatmap = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" },
                     }),
                     new DrawableRoom(new Room
                     {
                         Name = @"Relax It's The Weekend",
                         Host = new User{ Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }},
-                        Status = MultiplayerRoomStatus.Playing,
+                        Status = RoomStatus.Playing,
                         Beatmap = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" },
                     }),
                 }
@@ -48,7 +48,7 @@ namespace osu.Desktop.VisualTests.Tests
 
             AddStep(@"change state", () =>
             {
-                p.Room.Value.Status = MultiplayerRoomStatus.Playing;
+                p.Room.Value.Status = RoomStatus.Playing;
                 p.Room.TriggerChange();
             });
 
@@ -72,7 +72,7 @@ namespace osu.Desktop.VisualTests.Tests
 
             AddStep(@"change state", () =>
             {
-                p.Room.Value.Status = MultiplayerRoomStatus.Open;
+                p.Room.Value.Status = RoomStatus.Open;
                 p.Room.TriggerChange();
             });
         }
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
index 56ac71f6f9..12df7dc61c 100644
--- a/osu.Game/Online/Multiplayer/Room.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -11,11 +11,11 @@ namespace osu.Game.Online.Multiplayer
     {
         public string Name { get; set; }
         public User Host { get; set; }
-        public MultiplayerRoomStatus Status { get; set; }
+        public RoomStatus Status { get; set; }
         public BeatmapMetadata Beatmap { get; set; }
     }
 
-    public enum MultiplayerRoomStatus
+    public enum RoomStatus
     {
         [Description(@"Welcoming Players")]
         Open,
diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index b33100c900..db7dcb1a60 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -231,7 +231,7 @@ namespace osu.Game.Screens.Multiplayer
             }
 
             foreach (Drawable d in new Drawable[] { sideStrip, status })
-                d.FadeColour(room.Status == MultiplayerRoomStatus.Playing? playingColour : openColour, 100);
+                d.FadeColour(room.Status == RoomStatus.Playing? playingColour : openColour, 100);
         }
     }
 }

From 25b457e994947b6549fca71bf0b76ae3585714fe Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 13:05:18 -0300
Subject: [PATCH 087/179] Proper Bindable usage

---
 .../Tests/TestCaseDrawableRoom.cs             | 44 ++++++++---------
 osu.Game/Online/Multiplayer/Room.cs           |  9 ++--
 osu.Game/Screens/Multiplayer/DrawableRoom.cs  | 47 ++++++++++++-------
 3 files changed, 55 insertions(+), 45 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
index 08034ee4ae..4164e27195 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
@@ -19,7 +19,8 @@ namespace osu.Desktop.VisualTests.Tests
         {
             base.Reset();
 
-            DrawableRoom p;
+            DrawableRoom first;
+            DrawableRoom second;
             Add(new FillFlowContainer
             {
                 Anchor = Anchor.Centre,
@@ -29,51 +30,44 @@ namespace osu.Desktop.VisualTests.Tests
                 Direction = FillDirection.Vertical,
                 Children = new Drawable[]
                 {
-                    p = new DrawableRoom(new Room
-                    {
-                        Name = @"Great Room Right Here",
-                        Host = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }},
-                        Status = RoomStatus.Open,
-                        Beatmap = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" },
-                    }),
-                    new DrawableRoom(new Room
-                    {
-                        Name = @"Relax It's The Weekend",
-                        Host = new User{ Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }},
-                        Status = RoomStatus.Playing,
-                        Beatmap = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" },
-                    }),
+                    first = new DrawableRoom(new Room()),
+                    second = new DrawableRoom(new Room()),
                 }
             });
 
+            first.Room.Name.Value = @"Great Room Right Here";
+            first.Room.Host.Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }};
+            first.Room.Status.Value = RoomStatus.Open;
+            first.Room.Beatmap.Value = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" };
+
+            second.Room.Name.Value = @"Relax It's The Weekend";
+            second.Room.Host.Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }};
+            second.Room.Status.Value = RoomStatus.Playing;
+            second.Room.Beatmap.Value = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" };
+
             AddStep(@"change state", () =>
             {
-                p.Room.Value.Status = RoomStatus.Playing;
-                p.Room.TriggerChange();
+                first.Room.Status.Value = RoomStatus.Playing;
             });
 
             AddStep(@"change name", () =>
             {
-                p.Room.Value.Name = @"I Changed Name";
-                p.Room.TriggerChange();
+                first.Room.Name.Value = @"I Changed Name";
             });
 
             AddStep(@"change host", () =>
             {
-                p.Room.Value.Host = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } };
-                p.Room.TriggerChange();
+                first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } };
             });
 
             AddStep(@"change beatmap", () =>
             {
-                p.Room.Value.Beatmap = null;
-                p.Room.TriggerChange();
+                first.Room.Beatmap.Value = null;
             });
 
             AddStep(@"change state", () =>
             {
-                p.Room.Value.Status = RoomStatus.Open;
-                p.Room.TriggerChange();
+                first.Room.Status.Value = RoomStatus.Open;
             });
         }
     }
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
index 12df7dc61c..3dacf0862e 100644
--- a/osu.Game/Online/Multiplayer/Room.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.ComponentModel;
+using osu.Framework.Configuration;
 using osu.Game.Database;
 using osu.Game.Users;
 
@@ -9,10 +10,10 @@ namespace osu.Game.Online.Multiplayer
 {
     public class Room
     {
-        public string Name { get; set; }
-        public User Host { get; set; }
-        public RoomStatus Status { get; set; }
-        public BeatmapMetadata Beatmap { get; set; }
+        public Bindable<string> Name = new Bindable<string>();
+        public Bindable<User> Host = new Bindable<User>();
+        public Bindable<RoomStatus> Status = new Bindable<RoomStatus>();
+        public Bindable<BeatmapMetadata> Beatmap = new Bindable<BeatmapMetadata>();
     }
 
     public enum RoomStatus
diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index db7dcb1a60..5e81c46282 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Localisation;
+using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Online.Multiplayer;
@@ -39,11 +40,11 @@ namespace osu.Game.Screens.Multiplayer
         private Color4 playingColour;
         private LocalisationEngine localisation;
 
-        public readonly Bindable<Room> Room;
+        public readonly Room Room;
 
         public DrawableRoom(Room room)
         {
-            Room = new Bindable<Room>(room);
+            Room = room;
 
             RelativeSizeAxes = Axes.X;
             Height = height;
@@ -190,7 +191,10 @@ namespace osu.Game.Screens.Multiplayer
                 },
             };
 
-            Room.ValueChanged += displayRoom;
+            Room.Name.ValueChanged += displayName;
+            Room.Host.ValueChanged += displayUser;
+            Room.Status.ValueChanged += displayStatus;
+            Room.Beatmap.ValueChanged += displayBeatmap;
         }
 
         [BackgroundDependencyLoader]
@@ -203,22 +207,36 @@ namespace osu.Game.Screens.Multiplayer
             beatmapInfoFlow.Colour = rankBounds.Colour = colours.Gray9;
             host.Colour = colours.Blue;
 
-            Room.TriggerChange();
+            displayStatus(Room.Status.Value);
         }
 
-        private void displayRoom(Room room)
+        private void displayName(string value)
         {
-            name.Text = room.Name;
-            status.Text = room.Status.GetDescription();
-            host.Text = room.Host.Username;
-            flagContainer.Children = new[] { new DrawableFlag(room.Host.Country?.FlagName ?? @"__") { RelativeSizeAxes = Axes.Both } };
-            avatar.User = room.Host;
+            name.Text = value;
+        }
 
-            if (room.Beatmap != null)
+        private void displayUser(User value)
+        {
+            avatar.User = value;
+            host.Text = value.Username;
+            flagContainer.Children = new[] { new DrawableFlag(value.Country?.FlagName ?? @"__") { RelativeSizeAxes = Axes.Both } };
+        }
+
+        private void displayStatus(RoomStatus value)
+        {
+            status.Text = value.GetDescription() ?? value.ToString();
+
+            foreach (Drawable d in new Drawable[] { sideStrip, status })
+                d.FadeColour(value == RoomStatus.Playing ? playingColour : openColour, 100);
+        }
+
+        private void displayBeatmap(BeatmapMetadata value)
+        {
+            if (value != null)
             {
-                beatmapTitle.Current = localisation.GetUnicodePreference(room.Beatmap.TitleUnicode, room.Beatmap.Title);
+                beatmapTitle.Current = localisation.GetUnicodePreference(value.TitleUnicode, value.Title);
                 beatmapDash.Text = @" - ";
-                beatmapArtist.Current = localisation.GetUnicodePreference(room.Beatmap.ArtistUnicode, room.Beatmap.Artist);
+                beatmapArtist.Current = localisation.GetUnicodePreference(value.ArtistUnicode, value.Artist);
             }
             else
             {
@@ -229,9 +247,6 @@ namespace osu.Game.Screens.Multiplayer
                 beatmapDash.Text = string.Empty;
                 beatmapArtist.Text = string.Empty;
             }
-
-            foreach (Drawable d in new Drawable[] { sideStrip, status })
-                d.FadeColour(room.Status == RoomStatus.Playing? playingColour : openColour, 100);
         }
     }
 }

From 8b505a9e8b2458beefbeab5efe017026df6c7e87 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 13:06:52 -0300
Subject: [PATCH 088/179] Remove tab character

---
 osu.Game/Users/UserPanel.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 81e28e2735..6eff471989 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -159,9 +159,9 @@ namespace osu.Game.Users
                 },
             };
 
-            cover.Add(new AsyncLoadWrapper(new CoverBackgroundSprite(user)
-            {
-            	Anchor = Anchor.Centre,
+            cover.Add(new AsyncLoadWrapper(new CoverBackgroundSprite(user)
+            {
+                Anchor = Anchor.Centre,
                 Origin = Anchor.Centre,
                 FillMode = FillMode.Fill,
                 OnLoadComplete = d => d.FadeInFromZero(200),

From cf0e7887b57618ca01700b36054929a5b2ebd70f Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 13:14:56 -0300
Subject: [PATCH 089/179] Unused using

---
 osu.Game/Screens/Multiplayer/DrawableRoom.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index 5e81c46282..f3ff88b086 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -4,7 +4,6 @@
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
-using osu.Framework.Configuration;
 using osu.Framework.Extensions;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;

From 4681beab5d2c64ccf1b958378dceb3eb0c4736f0 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 13:22:14 -0300
Subject: [PATCH 090/179] CI fixes

---
 osu.Game/Users/UserPanel.cs | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 6eff471989..3502443d49 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -21,10 +21,8 @@ namespace osu.Game.Users
         private const float content_padding = 10;
         private const float status_height = 30;
 
-        private readonly User user;
         private OsuColour colours;
 
-        private readonly Container cover;
         private readonly Box statusBg;
         private readonly OsuSpriteText statusMessage;
 
@@ -32,8 +30,6 @@ namespace osu.Game.Users
 
         public UserPanel(User user)
         {
-            this.user = user;
-
             Width = 300;
             Height = height;
             Masking = true;
@@ -45,6 +41,7 @@ namespace osu.Game.Users
                 Radius = 4,
             };
 
+            Container cover;
             Children = new Drawable[]
             {
                 cover = new Container

From 9798117d53462e9aefea28c67be643ceaa3dbdb0 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Mon, 22 May 2017 21:13:57 -0300
Subject: [PATCH 091/179] Move RoomStatus to a class instead of enum

---
 .../Tests/TestCaseDrawableRoom.cs             |  8 +++---
 osu.Game/Online/Multiplayer/Room.cs           | 12 +--------
 osu.Game/Online/Multiplayer/RoomStatus.cs     | 26 +++++++++++++++++++
 osu.Game/Screens/Multiplayer/DrawableRoom.cs  | 12 ++++-----
 osu.Game/osu.Game.csproj                      |  1 +
 5 files changed, 37 insertions(+), 22 deletions(-)
 create mode 100644 osu.Game/Online/Multiplayer/RoomStatus.cs

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
index 4164e27195..de58323abe 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
@@ -37,17 +37,17 @@ namespace osu.Desktop.VisualTests.Tests
 
             first.Room.Name.Value = @"Great Room Right Here";
             first.Room.Host.Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }};
-            first.Room.Status.Value = RoomStatus.Open;
+            first.Room.Status.Value = new RoomStatusOpen();
             first.Room.Beatmap.Value = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" };
 
             second.Room.Name.Value = @"Relax It's The Weekend";
             second.Room.Host.Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }};
-            second.Room.Status.Value = RoomStatus.Playing;
+            second.Room.Status.Value = new RoomStatusPlaying();
             second.Room.Beatmap.Value = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" };
 
             AddStep(@"change state", () =>
             {
-                first.Room.Status.Value = RoomStatus.Playing;
+                first.Room.Status.Value = new RoomStatusPlaying();
             });
 
             AddStep(@"change name", () =>
@@ -67,7 +67,7 @@ namespace osu.Desktop.VisualTests.Tests
 
             AddStep(@"change state", () =>
             {
-                first.Room.Status.Value = RoomStatus.Open;
+                first.Room.Status.Value = new RoomStatusOpen();
             });
         }
     }
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
index 3dacf0862e..c82025f902 100644
--- a/osu.Game/Online/Multiplayer/Room.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -1,7 +1,6 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// 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.ComponentModel;
 using osu.Framework.Configuration;
 using osu.Game.Database;
 using osu.Game.Users;
@@ -15,13 +14,4 @@ namespace osu.Game.Online.Multiplayer
         public Bindable<RoomStatus> Status = new Bindable<RoomStatus>();
         public Bindable<BeatmapMetadata> Beatmap = new Bindable<BeatmapMetadata>();
     }
-
-    public enum RoomStatus
-    {
-        [Description(@"Welcoming Players")]
-        Open,
-
-        [Description(@"Now Playing")]
-        Playing,
-    }
 }
diff --git a/osu.Game/Online/Multiplayer/RoomStatus.cs b/osu.Game/Online/Multiplayer/RoomStatus.cs
new file mode 100644
index 0000000000..4f943596a7
--- /dev/null
+++ b/osu.Game/Online/Multiplayer/RoomStatus.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
+using osu.Game.Graphics;
+
+namespace osu.Game.Online.Multiplayer
+{
+    public abstract class RoomStatus
+    {
+        public abstract string Message { get; }
+        public abstract Color4 GetAppropriateColour(OsuColour colours);
+    }
+
+    public class RoomStatusOpen : RoomStatus
+    {
+        public override string Message => @"Welcoming Players";
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
+    }
+
+    public class RoomStatusPlaying : RoomStatus
+    {
+        public override string Message => @"Now Playing";
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
+    }
+}
diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index f3ff88b086..e4e781b839 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -4,7 +4,6 @@
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
-using osu.Framework.Extensions;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
@@ -35,8 +34,7 @@ namespace osu.Game.Screens.Multiplayer
         private readonly OsuSpriteText beatmapDash;
         private readonly OsuSpriteText beatmapArtist;
 
-        private Color4 openColour;
-        private Color4 playingColour;
+        private OsuColour colours;
         private LocalisationEngine localisation;
 
         public readonly Room Room;
@@ -200,9 +198,8 @@ namespace osu.Game.Screens.Multiplayer
         private void load(OsuColour colours, LocalisationEngine localisation)
         {
             this.localisation = localisation;
+            this.colours = colours;
 
-            openColour = colours.GreenLight;
-            playingColour = colours.Purple;
             beatmapInfoFlow.Colour = rankBounds.Colour = colours.Gray9;
             host.Colour = colours.Blue;
 
@@ -223,10 +220,11 @@ namespace osu.Game.Screens.Multiplayer
 
         private void displayStatus(RoomStatus value)
         {
-            status.Text = value.GetDescription() ?? value.ToString();
+            if (value == null) return;
+            status.Text = value.Message;
 
             foreach (Drawable d in new Drawable[] { sideStrip, status })
-                d.FadeColour(value == RoomStatus.Playing ? playingColour : openColour, 100);
+                d.FadeColour(value.GetAppropriateColour(colours), 100);
         }
 
         private void displayBeatmap(BeatmapMetadata value)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 7af7bdf1e6..2d2894cdca 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -427,6 +427,7 @@
     <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
     <Compile Include="Screens\Multiplayer\DrawableRoom.cs" />
     <Compile Include="Online\Multiplayer\Room.cs" />
+    <Compile Include="Online\Multiplayer\RoomStatus.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

From 3e0aaa1aa0ab8a3bf1bda52674b0d82c63ec313d Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 23 May 2017 12:29:43 +0900
Subject: [PATCH 092/179] Add basic beat response to osu! logo

---
 .../Containers/BeatSyncedContainer.cs         |   9 +-
 osu.Game/Screens/Menu/OsuLogo.cs              | 173 ++++++++++--------
 2 files changed, 106 insertions(+), 76 deletions(-)

diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index dfb742f7d1..b9a7183338 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -16,12 +16,19 @@ namespace osu.Game.Graphics.Containers
         private int lastBeat;
         private ControlPoint lastControlPoint;
 
+        /// <summary>
+        /// The amount of time before a beat we should fire <see cref="OnNewBeat(int, double, TimeSignatures, bool)"/>.
+        /// This allows for adding easing to animations that may be synchronised to the beat.
+        /// </summary>
+        protected double EarlyActivationMilliseconds;
+
         protected override void Update()
         {
             if (beatmap.Value?.Track == null)
                 return;
 
-            double currentTrackTime = beatmap.Value.Track.CurrentTime;
+            double currentTrackTime = beatmap.Value.Track.CurrentTime + EarlyActivationMilliseconds;
+
             ControlPoint overridePoint;
             ControlPoint controlPoint = beatmap.Value.Beatmap.TimingInfo.TimingPointAt(currentTrackTime, out overridePoint);
 
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index e28adeacff..5f9a3bf655 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -12,21 +12,24 @@ using osu.Framework.Graphics.Textures;
 using osu.Framework.Input;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
 using OpenTK;
 using OpenTK.Graphics;
+using osu.Game.Beatmaps.Timing;
 
 namespace osu.Game.Screens.Menu
 {
     /// <summary>
     /// osu! logo and its attachments (pulsing, visualiser etc.)
     /// </summary>
-    public class OsuLogo : Container
+    public class OsuLogo : BeatSyncedContainer
     {
         public readonly Color4 OsuPink = OsuColour.FromHex(@"e967a1");
 
         private readonly Sprite logo;
         private readonly CircularContainer logoContainer;
         private readonly Container logoBounceContainer;
+        private readonly Container logoBeatContainer;
         private readonly Container logoHoverContainer;
 
         private SampleChannel sampleClick;
@@ -67,8 +70,12 @@ namespace osu.Game.Screens.Menu
 
         private const float default_size = 480;
 
+        private const double beat_in_time = 60;
+
         public OsuLogo()
         {
+            EarlyActivationMilliseconds = beat_in_time;
+
             Size = new Vector2(default_size);
 
             Anchor = Anchor.Centre;
@@ -78,109 +85,116 @@ namespace osu.Game.Screens.Menu
 
             Children = new Drawable[]
             {
-                logoBounceContainer = new Container
+                logoBeatContainer  = new Container
                 {
                     AutoSizeAxes = Axes.Both,
                     Children = new Drawable[]
                     {
-                        logoHoverContainer = new Container
+                        logoBounceContainer = new Container
                         {
                             AutoSizeAxes = Axes.Both,
                             Children = new Drawable[]
                             {
-                                new BufferedContainer
+                                logoHoverContainer = new Container
                                 {
                                     AutoSizeAxes = Axes.Both,
                                     Children = new Drawable[]
                                     {
-                                        logoContainer = new CircularContainer
+                                        new BufferedContainer
+                                        {
+                                            AutoSizeAxes = Axes.Both,
+                                            Children = new Drawable[]
+                                            {
+                                                logoContainer = new CircularContainer
+                                                {
+                                                    Anchor = Anchor.Centre,
+                                                    Origin = Anchor.Centre,
+                                                    RelativeSizeAxes = Axes.Both,
+                                                    Scale = new Vector2(0.88f),
+                                                    Masking = true,
+                                                    Children = new Drawable[]
+                                                    {
+                                                        colourAndTriangles = new Container
+                                                        {
+                                                            RelativeSizeAxes = Axes.Both,
+                                                            Anchor = Anchor.Centre,
+                                                            Origin = Anchor.Centre,
+                                                            Children = new Drawable[]
+                                                            {
+                                                                new Box
+                                                                {
+                                                                    RelativeSizeAxes = Axes.Both,
+                                                                    Colour = OsuPink,
+                                                                },
+                                                                new Triangles
+                                                                {
+                                                                    TriangleScale = 4,
+                                                                    ColourLight = OsuColour.FromHex(@"ff7db7"),
+                                                                    ColourDark = OsuColour.FromHex(@"de5b95"),
+                                                                    RelativeSizeAxes = Axes.Both,
+                                                                },
+                                                            }
+                                                        },
+                                                        flashLayer = new Box
+                                                        {
+                                                            RelativeSizeAxes = Axes.Both,
+                                                            BlendingMode = BlendingMode.Additive,
+                                                            Colour = Color4.White,
+                                                            Alpha = 0,
+                                                        },
+                                                    },
+                                                },
+                                                logo = new Sprite
+                                                {
+                                                    Anchor = Anchor.Centre,
+                                                    Origin = Anchor.Centre,
+                                                },
+                                            }
+                                        },
+                                        rippleContainer = new Container
                                         {
                                             Anchor = Anchor.Centre,
                                             Origin = Anchor.Centre,
                                             RelativeSizeAxes = Axes.Both,
-                                            Scale = new Vector2(0.88f),
+                                            Children = new Drawable[]
+                                            {
+                                                ripple = new Sprite
+                                                {
+                                                    Anchor = Anchor.Centre,
+                                                    Origin = Anchor.Centre,
+                                                    BlendingMode = BlendingMode.Additive,
+                                                    Alpha = 0.15f
+                                                }
+                                            }
+                                        },
+                                        impactContainer = new CircularContainer
+                                        {
+                                            Anchor = Anchor.Centre,
+                                            Origin = Anchor.Centre,
+                                            Alpha = 0,
+                                            BorderColour = Color4.White,
+                                            RelativeSizeAxes = Axes.Both,
+                                            BorderThickness = 10,
                                             Masking = true,
                                             Children = new Drawable[]
                                             {
-                                                colourAndTriangles = new Container
+                                                new Box
                                                 {
                                                     RelativeSizeAxes = Axes.Both,
-                                                    Anchor = Anchor.Centre,
-                                                    Origin = Anchor.Centre,
-                                                    Children = new Drawable[]
-                                                    {
-                                                        new Box
-                                                        {
-                                                            RelativeSizeAxes = Axes.Both,
-                                                            Colour = OsuPink,
-                                                        },
-                                                        new Triangles
-                                                        {
-                                                            TriangleScale = 4,
-                                                            ColourLight = OsuColour.FromHex(@"ff7db7"),
-                                                            ColourDark = OsuColour.FromHex(@"de5b95"),
-                                                            RelativeSizeAxes = Axes.Both,
-                                                        },
-                                                    }
-                                                },
-                                                flashLayer = new Box
-                                                {
-                                                    RelativeSizeAxes = Axes.Both,
-                                                    BlendingMode = BlendingMode.Additive,
-                                                    Colour = Color4.White,
+                                                    AlwaysPresent = true,
                                                     Alpha = 0,
-                                                },
-                                            },
+                                                }
+                                            }
                                         },
-                                        logo = new Sprite
+                                        new MenuVisualisation
                                         {
                                             Anchor = Anchor.Centre,
                                             Origin = Anchor.Centre,
-                                        },
-                                    }
-                                },
-                                rippleContainer = new Container
-                                {
-                                    Anchor = Anchor.Centre,
-                                    Origin = Anchor.Centre,
-                                    RelativeSizeAxes = Axes.Both,
-                                    Children = new Drawable[]
-                                    {
-                                        ripple = new Sprite
-                                        {
-                                            Anchor = Anchor.Centre,
-                                            Origin = Anchor.Centre,
-                                            BlendingMode = BlendingMode.Additive,
-                                            Alpha = 0.15f
-                                        }
-                                    }
-                                },
-                                impactContainer = new CircularContainer
-                                {
-                                    Anchor = Anchor.Centre,
-                                    Origin = Anchor.Centre,
-                                    Alpha = 0,
-                                    BorderColour = Color4.White,
-                                    RelativeSizeAxes = Axes.Both,
-                                    BorderThickness = 10,
-                                    Masking = true,
-                                    Children = new Drawable[]
-                                    {
-                                        new Box
-                                        {
                                             RelativeSizeAxes = Axes.Both,
-                                            AlwaysPresent = true,
-                                            Alpha = 0,
+                                            BlendingMode = BlendingMode.Additive,
+                                            Alpha = 0.2f,
                                         }
                                     }
-                                },
-                                new MenuVisualisation
-                                {
-                                    Anchor = Anchor.Centre,
-                                    Origin = Anchor.Centre,
-                                    RelativeSizeAxes = Axes.Both,
-                                    BlendingMode = BlendingMode.Additive,
-                                    Alpha = 0.2f,
                                 }
                             }
                         }
@@ -206,6 +220,15 @@ namespace osu.Game.Screens.Menu
             ripple.Loop(300);
         }
 
+        protected override void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
+        {
+            base.OnNewBeat(newBeat, beatLength, timeSignature, kiai);
+
+            logoBeatContainer.ScaleTo(0.97f, beat_in_time, EasingTypes.Out);
+            using (logoBeatContainer.BeginDelayedSequence(beat_in_time))
+                logoBeatContainer.ScaleTo(1, beatLength * 2, EasingTypes.OutQuint);
+        }
+
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
         {
             if (!Interactive) return false;

From 3cdfd2eef5ca0af577503b8859d1de1792fe1362 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 13:55:18 +0900
Subject: [PATCH 093/179] Split ControlPoint into different types.

# Conflicts:
#	osu.Game.Rulesets.Mania/UI/Column.cs
---
 osu-framework                                 |  2 +-
 .../Tests/TestCaseGamefield.cs                |  7 +-
 .../Tests/TestCaseManiaPlayfield.cs           |  6 +-
 .../Legacy/DistanceObjectPatternGenerator.cs  | 12 ++-
 osu.Game.Rulesets.Mania/Objects/HoldNote.cs   |  5 +-
 osu.Game.Rulesets.Mania/Objects/Note.cs       |  5 +-
 .../Timing/ControlPointContainer.cs           |  9 ++-
 .../Timing/TimingChange.cs                    | 23 ++++++
 osu.Game.Rulesets.Mania/UI/Column.cs          |  3 +-
 .../UI/ManiaHitRenderer.cs                    | 31 ++++---
 osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs  |  3 +-
 .../osu.Game.Rulesets.Mania.csproj            |  1 +
 osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs |  5 +-
 osu.Game.Rulesets.Osu/Objects/Slider.cs       | 12 ++-
 osu.Game.Rulesets.Osu/Objects/Spinner.cs      |  5 +-
 .../Beatmaps/TaikoBeatmapConverter.cs         |  8 +-
 osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs   |  9 ++-
 osu.Game.Rulesets.Taiko/Objects/Hit.cs        |  5 +-
 .../Objects/TaikoHitObject.cs                 | 15 ++--
 .../UI/TaikoHitRenderer.cs                    |  6 +-
 osu.Game/Beatmaps/Beatmap.cs                  |  5 +-
 .../Beatmaps/ControlPoints/ControlPoint.cs    | 10 +++
 .../ControlPoints/ControlPointInfo.cs         | 63 +++++++++++++++
 .../ControlPoints/DifficultyControlPoint.cs   |  7 ++
 .../ControlPoints/EffectControlPoint.cs       |  8 ++
 .../ControlPoints/SoundControlPoint.cs        |  8 ++
 .../ControlPoints/TimingControlPoint.cs       | 10 +++
 osu.Game/Beatmaps/DifficultyCalculator.cs     |  2 +-
 osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 56 ++++++++++---
 osu.Game/Beatmaps/Timing/ControlPoint.cs      | 20 -----
 osu.Game/Beatmaps/Timing/TimingInfo.cs        | 80 -------------------
 .../Containers/BeatSyncedContainer.cs         | 31 +++----
 .../Rulesets/Beatmaps/BeatmapConverter.cs     |  2 +-
 osu.Game/Rulesets/Objects/HitObject.cs        | 20 +++--
 osu.Game/Rulesets/UI/HitRenderer.cs           |  2 +-
 osu.Game/Screens/Play/Player.cs               |  2 +-
 osu.Game/Screens/Select/BeatmapInfoWedge.cs   |  6 +-
 osu.Game/osu.Game.csproj                      |  8 +-
 38 files changed, 307 insertions(+), 205 deletions(-)
 create mode 100644 osu.Game.Rulesets.Mania/Timing/TimingChange.cs
 create mode 100644 osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
 create mode 100644 osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
 create mode 100644 osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
 create mode 100644 osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
 create mode 100644 osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs
 create mode 100644 osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
 delete mode 100644 osu.Game/Beatmaps/Timing/ControlPoint.cs
 delete mode 100644 osu.Game/Beatmaps/Timing/TimingInfo.cs

diff --git a/osu-framework b/osu-framework
index 42e26d49b9..773d60eb6b 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 42e26d49b9046fcb96c123b0dfb48e06d741e162
+Subproject commit 773d60eb6b811f395e32a22dc66bb4d2e63a6dbc
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
index 6bd9d35b80..fb5bd8cde4 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
@@ -19,6 +19,7 @@ using System.Collections.Generic;
 using osu.Desktop.VisualTests.Beatmaps;
 using osu.Framework.Allocation;
 using osu.Game.Beatmaps.Timing;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Desktop.VisualTests.Tests
 {
@@ -53,8 +54,8 @@ namespace osu.Desktop.VisualTests.Tests
                 time += RNG.Next(50, 500);
             }
 
-            TimingInfo timing = new TimingInfo();
-            timing.ControlPoints.Add(new ControlPoint
+            var controlPointInfo = new ControlPointInfo();
+            controlPointInfo.ControlPoints.Add(new TimingControlPoint
             {
                 BeatLength = 200
             });
@@ -73,7 +74,7 @@ namespace osu.Desktop.VisualTests.Tests
                         Author = @"peppy",
                     },
                 },
-                TimingInfo = timing
+                ControlPointInfo = controlPointInfo
             });
 
             Add(new Drawable[]
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
index 04fcd8e94a..88f90fc333 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
@@ -11,6 +11,8 @@ using osu.Game.Beatmaps.Timing;
 using OpenTK;
 using osu.Game.Rulesets.Mania.Objects.Drawables;
 using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Timing;
 
 namespace osu.Desktop.VisualTests.Tests
 {
@@ -27,7 +29,7 @@ namespace osu.Desktop.VisualTests.Tests
             Action<int, SpecialColumnPosition> createPlayfield = (cols, pos) =>
             {
                 Clear();
-                Add(new ManiaPlayfield(cols, new List<ControlPoint>())
+                Add(new ManiaPlayfield(cols, new List<TimingChange>())
                 {
                     Anchor = Anchor.Centre,
                     Origin = Anchor.Centre,
@@ -41,7 +43,7 @@ namespace osu.Desktop.VisualTests.Tests
                 Clear();
 
                 ManiaPlayfield playField;
-                Add(playField = new ManiaPlayfield(cols, new List<ControlPoint> { new ControlPoint { BeatLength = 200 } })
+                Add(playField = new ManiaPlayfield(cols, new List<TimingChange>())
                 {
                     Anchor = Anchor.Centre,
                     Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 0cad23304e..1209a5c879 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mania.MathUtils;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 {
@@ -32,11 +33,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
             : base(random, hitObject, beatmap, previousPattern)
         {
-            ControlPoint overridePoint;
-            ControlPoint controlPoint = Beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint);
-
             convertType = PatternType.None;
-            if ((overridePoint ?? controlPoint)?.KiaiMode == false)
+            if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
                 convertType = PatternType.LowProbability;
 
             var distanceData = hitObject as IHasDistance;
@@ -44,13 +42,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
             repeatCount = repeatsData?.RepeatCount ?? 1;
 
-            double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(hitObject.StartTime);
-            double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment;
+            TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
+            DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
 
             // The true distance, accounting for any repeats
             double distance = (distanceData?.Distance ?? 0) * repeatCount;
             // The velocity of the osu! hit object - calculated as the velocity of a slider
-            double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength;
+            double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier);
             // The duration of the osu! hit object
             double osuDuration = distance / osuVelocity;
 
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 41bbe08d56..b0a0ed10b8 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Mania.Judgements;
@@ -33,9 +34,9 @@ namespace osu.Game.Rulesets.Mania.Objects
         /// </summary>
         public HitWindows ReleaseHitWindows { get; protected set; } = new HitWindows();
 
-        public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            base.ApplyDefaults(timing, difficulty);
+            base.ApplyDefaults(controlPointInfo, difficulty);
 
             ReleaseHitWindows = HitWindows * release_window_lenience;
         }
diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs
index e955f6658b..e91c00c145 100644
--- a/osu.Game.Rulesets.Mania/Objects/Note.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Note.cs
@@ -1,6 +1,7 @@
 // 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.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Mania.Judgements;
@@ -17,9 +18,9 @@ namespace osu.Game.Rulesets.Mania.Objects
         /// </summary>
         public HitWindows HitWindows { get; protected set; } = new HitWindows();
 
-        public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            base.ApplyDefaults(timing, difficulty);
+            base.ApplyDefaults(controlPointInfo, difficulty);
 
             HitWindows = new HitWindows(difficulty.OverallDifficulty);
         }
diff --git a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
index 2ff97047c0..38e975f468 100644
--- a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
+++ b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using OpenTK;
 using osu.Game.Beatmaps.Timing;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Mania.Timing
 {
@@ -26,9 +27,9 @@ namespace osu.Game.Rulesets.Mania.Timing
         /// </summary>
         public double TimeSpan { get; set; }
 
-        private readonly List<DrawableControlPoint> drawableControlPoints;
+        private readonly List<DrawableControlPoint> drawableControlPoints = new List<DrawableControlPoint>();
 
-        public ControlPointContainer(IEnumerable<ControlPoint> timingChanges)
+        public ControlPointContainer(IEnumerable<TimingChange> timingChanges)
         {
             drawableControlPoints = timingChanges.Select(t => new DrawableControlPoint(t)).ToList();
             Children = drawableControlPoints;
@@ -64,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Timing
         /// </summary>
         private class DrawableControlPoint : Container
         {
-            private readonly ControlPoint timingChange;
+            private readonly TimingChange timingChange;
 
             protected override Container<Drawable> Content => content;
             private readonly Container content;
@@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Timing
             /// the content container will scroll at twice the normal rate.
             /// </summary>
             /// <param name="timingChange">The control point to create the drawable control point for.</param>
-            public DrawableControlPoint(ControlPoint timingChange)
+            public DrawableControlPoint(TimingChange timingChange)
             {
                 this.timingChange = timingChange;
 
diff --git a/osu.Game.Rulesets.Mania/Timing/TimingChange.cs b/osu.Game.Rulesets.Mania/Timing/TimingChange.cs
new file mode 100644
index 0000000000..fb6f1d2db1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Timing/TimingChange.cs
@@ -0,0 +1,23 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Rulesets.Mania.Timing
+{
+    public class TimingChange
+    {
+        /// <summary>
+        /// The time at which this timing change happened.
+        /// </summary>
+        public double Time;
+
+        /// <summary>
+        /// The beat length.
+        /// </summary>
+        public double BeatLength = 500;
+
+        /// <summary>
+        /// The speed multiplier.
+        /// </summary>
+        public double SpeedMultiplier = 1;
+    }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 72c60b28c9..12c4cea58c 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -18,6 +18,7 @@ using osu.Game.Rulesets.Objects.Drawables;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mania.Judgements;
 using osu.Game.Beatmaps.Timing;
+using osu.Game.Beatmaps.ControlPoints;
 using System;
 using osu.Framework.Configuration;
 
@@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.UI
 
         public readonly ControlPointContainer ControlPointContainer;
 
-        public Column(IEnumerable<ControlPoint> timingChanges)
+        public Column(IEnumerable<TimingChange> timingChanges)
         {
             RelativeSizeAxes = Axes.Y;
             Width = column_width;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
index 4d734d231f..8d5e22933c 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
@@ -8,6 +8,7 @@ using OpenTK.Input;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Rulesets.Beatmaps;
 using osu.Game.Rulesets.Mania.Beatmaps;
@@ -15,6 +16,7 @@ using osu.Game.Rulesets.Mania.Judgements;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mania.Objects.Drawables;
 using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mania.Timing;
 using osu.Game.Rulesets.Objects.Drawables;
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Scoring;
@@ -33,22 +35,29 @@ namespace osu.Game.Rulesets.Mania.UI
 
         protected override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield()
         {
-            ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange);
-
-            if (firstTimingChange == null)
-                throw new InvalidOperationException("The Beatmap contains no timing points!");
+            double lastSpeedMultiplier = 1;
+            double lastBeatLength = 500;
 
             // Generate the timing points, making non-timing changes use the previous timing change
-            var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c =>
+            var timingChanges = Beatmap.ControlPointInfo.ControlPoints.Where(c => c is TimingControlPoint || c is DifficultyControlPoint).Select(c =>
             {
-                ControlPoint t = c.Clone();
+                var change = new TimingChange();
 
-                if (c.TimingChange)
-                    firstTimingChange = c;
-                else
-                    t.BeatLength = firstTimingChange.BeatLength;
+                var timingPoint = c as TimingControlPoint;
+                var difficultyPoint = c as DifficultyControlPoint;
 
-                return t;
+                if (timingPoint != null)
+                    lastBeatLength = timingPoint.BeatLength;
+
+                if (difficultyPoint != null)
+                    lastSpeedMultiplier = difficultyPoint.SpeedMultiplier;
+
+                return new TimingChange
+                {
+                    Time = c.Time,
+                    BeatLength = lastBeatLength,
+                    SpeedMultiplier = lastSpeedMultiplier
+                };
             });
 
             double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 70bdd3b13c..c0915c5d82 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -22,6 +22,7 @@ using osu.Framework.Input;
 using osu.Game.Beatmaps.Timing;
 using osu.Framework.Graphics.Transforms;
 using osu.Framework.MathUtils;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Mania.UI
 {
@@ -65,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.UI
 
         private readonly int columnCount;
 
-        public ManiaPlayfield(int columnCount, IEnumerable<ControlPoint> timingChanges)
+        public ManiaPlayfield(int columnCount, IEnumerable<TimingChange> timingChanges)
         {
             this.columnCount = columnCount;
 
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index a3f30acae0..9442d7cf8f 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -78,6 +78,7 @@
     <Compile Include="Mods\ManiaMod.cs" />
     <Compile Include="UI\SpecialColumnPosition.cs" />
     <Compile Include="Timing\ControlPointContainer.cs" />
+    <Compile Include="Timing\TimingChange.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 723a37ed7b..c4b4e3fc4a 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -8,6 +8,7 @@ using osu.Game.Rulesets.Objects.Types;
 using OpenTK.Graphics;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Osu.Objects
 {
@@ -68,9 +69,9 @@ namespace osu.Game.Rulesets.Osu.Objects
             return OsuScoreResult.Miss;
         }
 
-        public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            base.ApplyDefaults(timing, difficulty);
+            base.ApplyDefaults(controlPointInfo, difficulty);
 
             Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
         }
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 6c0147a3de..1a5065d17d 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects;
 using osu.Game.Database;
 using System.Linq;
 using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Osu.Objects
 {
@@ -62,13 +63,16 @@ namespace osu.Game.Rulesets.Osu.Objects
         public double Velocity;
         public double TickDistance;
 
-        public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            base.ApplyDefaults(timing, difficulty);
+            base.ApplyDefaults(controlPointInfo, difficulty);
 
-            double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / timing.SpeedMultiplierAt(StartTime);
+            TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
+            DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
 
-            Velocity = scoringDistance / timing.BeatLengthAt(StartTime);
+            double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier;
+
+            Velocity = scoringDistance / timingPoint.BeatLength;
             TickDistance = scoringDistance / difficulty.SliderTickRate;
         }
 
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 3761b62b65..8c0a218108 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -4,6 +4,7 @@
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Osu.Objects
 {
@@ -19,9 +20,9 @@ namespace osu.Game.Rulesets.Osu.Objects
 
         public override bool NewCombo => true;
 
-        public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            base.ApplyDefaults(timing, difficulty);
+            base.ApplyDefaults(controlPointInfo, difficulty);
 
             SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
         }
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index a2dea3731e..5d44da78f9 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -12,6 +12,7 @@ using osu.Game.Database;
 using osu.Game.IO.Serialization;
 using osu.Game.Audio;
 using osu.Game.Rulesets.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Taiko.Beatmaps
 {
@@ -77,8 +78,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
             {
                 int repeats = repeatsData?.RepeatCount ?? 1;
 
-                double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime);
-                double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * speedAdjustment;
+                TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
+                DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
+
+                double speedAdjustment = difficultyPoint.SpeedMultiplier;
+                double speedAdjustedBeatLength = timingPoint.BeatLength * speedAdjustment;
 
                 // The true distance, accounting for any repeats. This ends up being the drum roll distance later
                 double distance = distanceData.Distance * repeats * legacy_velocity_multiplier;
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index f79c01b643..857dd8d3a5 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -8,6 +8,7 @@ using System.Linq;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Taiko.Objects
 {
@@ -55,11 +56,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
         /// </summary>
         private double tickSpacing = 100;
 
-        public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            base.ApplyDefaults(timing, difficulty);
+            base.ApplyDefaults(controlPointInfo, difficulty);
 
-            tickSpacing = timing.BeatLengthAt(StartTime) / TickRate;
+            TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
+
+            tickSpacing = timingPoint.BeatLength / TickRate;
 
             RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
             RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
index 136e89124c..cc15b5ab98 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
@@ -1,6 +1,7 @@
 // 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.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 
@@ -23,9 +24,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
         /// </summary>
         public double HitWindowMiss = 95;
 
-        public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            base.ApplyDefaults(timing, difficulty);
+            base.ApplyDefaults(controlPointInfo, difficulty);
 
             HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20);
             HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50);
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 6a6353fde2..a294a13521 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -1,6 +1,7 @@
 // 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.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Objects;
@@ -51,17 +52,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
         /// </summary>
         public bool Kiai { get; protected set; }
 
-        public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            base.ApplyDefaults(timing, difficulty);
+            base.ApplyDefaults(controlPointInfo, difficulty);
 
-            ScrollTime = scroll_time * (timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / 1000) / difficulty.SliderMultiplier;
+            TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
+            DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
+            EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime);
 
-            ControlPoint overridePoint;
-            Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode;
+            ScrollTime = scroll_time * (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier / 1000) / difficulty.SliderMultiplier;
 
-            if (overridePoint != null)
-                Kiai |= overridePoint.KiaiMode;
+            Kiai |= effectPoint.KiaiMode;
         }
     }
 }
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
index 73450d576d..7b0a0b085d 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
@@ -17,6 +17,8 @@ using osu.Game.Rulesets.UI;
 using osu.Game.Rulesets.Taiko.Replays;
 using OpenTK;
 using osu.Game.Rulesets.Beatmaps;
+using System.Linq;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Taiko.UI
 {
@@ -43,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.UI
             TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
             double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
 
-            var timingPoints = Beatmap.TimingInfo.ControlPoints.FindAll(cp => cp.TimingChange);
+            var timingPoints = Beatmap.ControlPointInfo.ControlPoints.OfType<TimingControlPoint>().ToList();
 
             if (timingPoints.Count == 0)
                 return;
@@ -68,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.UI
                     StartTime = time,
                 };
 
-                barLine.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty);
+                barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
 
                 bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
                 taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 608b2fcd19..0368455b92 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -7,6 +7,7 @@ using osu.Game.Database;
 using osu.Game.Rulesets.Objects;
 using System.Collections.Generic;
 using System.Linq;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Beatmaps
 {
@@ -17,7 +18,7 @@ namespace osu.Game.Beatmaps
         where T : HitObject
     {
         public BeatmapInfo BeatmapInfo;
-        public TimingInfo TimingInfo = new TimingInfo();
+        public ControlPointInfo ControlPointInfo = new ControlPointInfo();
         public List<BreakPeriod> Breaks = new List<BreakPeriod>();
         public readonly List<Color4> ComboColors = new List<Color4>
         {
@@ -46,7 +47,7 @@ namespace osu.Game.Beatmaps
         public Beatmap(Beatmap original = null)
         {
             BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
-            TimingInfo = original?.TimingInfo ?? TimingInfo;
+            ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
             Breaks = original?.Breaks ?? Breaks;
             ComboColors = original?.ComboColors ?? ComboColors;
         }
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
new file mode 100644
index 0000000000..5c31259de1
--- /dev/null
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -0,0 +1,10 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Beatmaps.ControlPoints
+{
+    public class ControlPoint
+    {
+        public double Time;
+    }
+}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
new file mode 100644
index 0000000000..90c50a3e05
--- /dev/null
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace osu.Game.Beatmaps.ControlPoints
+{
+    public class ControlPointInfo
+    {
+        /// <summary>
+        /// All the control points.
+        /// </summary>
+        public readonly List<ControlPoint> ControlPoints = new List<ControlPoint>();
+
+        /// <summary>
+        /// Finds the difficulty control point that is active at <paramref name="time"/>.
+        /// </summary>
+        /// <param name="time">The time to find the difficulty control point at.</param>
+        /// <returns>The difficulty control point.</returns>
+        public DifficultyControlPoint DifficultyPointAt(double time) =>
+            ControlPoints.OfType<DifficultyControlPoint>().LastOrDefault(t => t.Time <= time) ?? new DifficultyControlPoint();
+
+        /// <summary>
+        /// Finds the effect control point that is active at <paramref name="time"/>.
+        /// </summary>
+        /// <param name="time">The time to find the effect control point at.</param>
+        /// <returns>The effect control point.</returns>
+        public EffectControlPoint EffectPointAt(double time) =>
+            ControlPoints.OfType<EffectControlPoint>().LastOrDefault(t => t.Time <= time) ?? new EffectControlPoint();
+
+        /// <summary>
+        /// Finds the sound control point that is active at <paramref name="time"/>.
+        /// </summary>
+        /// <param name="time">The time to find the sound control point at.</param>
+        /// <returns>The sound control point.</returns>
+        public SoundControlPoint SoundPointAt(double time) =>
+            ControlPoints.OfType<SoundControlPoint>().LastOrDefault(t => t.Time <= time) ?? new SoundControlPoint();
+
+        /// <summary>
+        /// Finds the timing control point that is active at <paramref name="time"/>.
+        /// </summary>
+        /// <param name="time">The time to find the timing control point at.</param>
+        /// <returns>The timing control point.</returns>
+        public TimingControlPoint TimingPointAt(double time) =>
+            ControlPoints.OfType<TimingControlPoint>().LastOrDefault(t => t.Time <= time) ?? new TimingControlPoint();
+
+        /// <summary>
+        /// Finds the maximum BPM represented by any timing control point.
+        /// </summary>
+        public double BPMMaximum =>
+            60000 / (ControlPoints.OfType<TimingControlPoint>().OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+
+        /// <summary>
+        /// Finds the minimum BPM represented by any timing control point.
+        /// </summary>
+        public double BPMMinimum =>
+            60000 / (ControlPoints.OfType<TimingControlPoint>().OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+
+        /// <summary>
+        /// Finds the mode BPM (most common BPM) represented by the control points.
+        /// </summary>
+        public double BPMMode =>
+            60000 / (ControlPoints.OfType<TimingControlPoint>().GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
new file mode 100644
index 0000000000..abd9d05971
--- /dev/null
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -0,0 +1,7 @@
+namespace osu.Game.Beatmaps.ControlPoints
+{
+    public class DifficultyControlPoint : ControlPoint
+    {
+        public double SpeedMultiplier = 1;
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
new file mode 100644
index 0000000000..2f6003f57c
--- /dev/null
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -0,0 +1,8 @@
+namespace osu.Game.Beatmaps.ControlPoints
+{
+    public class EffectControlPoint : ControlPoint
+    {
+        public bool KiaiMode;
+        public bool OmitFirstBarLine;
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs
new file mode 100644
index 0000000000..696e684a3d
--- /dev/null
+++ b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs
@@ -0,0 +1,8 @@
+namespace osu.Game.Beatmaps.ControlPoints
+{
+    public class SoundControlPoint : ControlPoint
+    {
+        public string SampleBank;
+        public int SampleVolume;
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
new file mode 100644
index 0000000000..57c21f110b
--- /dev/null
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -0,0 +1,10 @@
+using osu.Game.Beatmaps.Timing;
+
+namespace osu.Game.Beatmaps.ControlPoints
+{
+    public class TimingControlPoint : ControlPoint
+    {
+        public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
+        public double BeatLength = 500;
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index f483d1e6e3..474d38aa1b 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps
             Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects;
 
             foreach (var h in Objects)
-                h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty);
+                h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
 
             PreprocessHitObjects();
         }
diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
index 04208337c7..e140e2282c 100644
--- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
@@ -8,6 +8,8 @@ using OpenTK.Graphics;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Objects.Legacy;
+using System.Linq;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Beatmaps.Formats
 {
@@ -241,6 +243,7 @@ namespace osu.Game.Beatmaps.Formats
 
             double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
             double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
+            double speedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1;
 
             TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
             if (split.Length >= 3)
@@ -275,18 +278,49 @@ namespace osu.Game.Beatmaps.Formats
             if (stringSampleSet == @"none")
                 stringSampleSet = @"normal";
 
-            beatmap.TimingInfo.ControlPoints.Add(new ControlPoint
+            TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
+            DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
+            SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
+            EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
+
+            if (timingChange && (beatLength != timingPoint.BeatLength || timeSignature != timingPoint.TimeSignature))
             {
-                Time = time,
-                BeatLength = beatLength,
-                SpeedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1,
-                TimingChange = timingChange,
-                TimeSignature = timeSignature,
-                SampleBank = stringSampleSet,
-                SampleVolume = sampleVolume,
-                KiaiMode = kiaiMode,
-                OmitFirstBarLine = omitFirstBarSignature
-            });
+                beatmap.ControlPointInfo.ControlPoints.Add(new TimingControlPoint
+                {
+                    Time = time,
+                    BeatLength = beatLength,
+                    TimeSignature = timeSignature
+                });
+            }
+
+            if (speedMultiplier != difficultyPoint.SpeedMultiplier)
+            {
+                beatmap.ControlPointInfo.ControlPoints.Add(new DifficultyControlPoint
+                {
+                    Time = time,
+                    SpeedMultiplier = speedMultiplier
+                });
+            }
+
+            if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
+            {
+                beatmap.ControlPointInfo.ControlPoints.Add(new SoundControlPoint
+                {
+                    Time = time,
+                    SampleBank = stringSampleSet,
+                    SampleVolume = sampleVolume
+                });
+            }
+
+            if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
+            {
+                beatmap.ControlPointInfo.ControlPoints.Add(new EffectControlPoint
+                {
+                    Time = time,
+                    KiaiMode = kiaiMode,
+                    OmitFirstBarLine = omitFirstBarSignature
+                });
+            }
         }
 
         private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours)
diff --git a/osu.Game/Beatmaps/Timing/ControlPoint.cs b/osu.Game/Beatmaps/Timing/ControlPoint.cs
deleted file mode 100644
index fbae7d9614..0000000000
--- a/osu.Game/Beatmaps/Timing/ControlPoint.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-namespace osu.Game.Beatmaps.Timing
-{
-    public class ControlPoint
-    {
-        public string SampleBank;
-        public int SampleVolume;
-        public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
-        public double Time;
-        public double BeatLength = 500;
-        public double SpeedMultiplier = 1;
-        public bool TimingChange = true;
-        public bool KiaiMode;
-        public bool OmitFirstBarLine;
-
-        public ControlPoint Clone() => (ControlPoint)MemberwiseClone();
-    }
-}
diff --git a/osu.Game/Beatmaps/Timing/TimingInfo.cs b/osu.Game/Beatmaps/Timing/TimingInfo.cs
deleted file mode 100644
index 19cb0816ba..0000000000
--- a/osu.Game/Beatmaps/Timing/TimingInfo.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.Collections.Generic;
-using System.Linq;
-
-namespace osu.Game.Beatmaps.Timing
-{
-    public class TimingInfo
-    {
-        public readonly List<ControlPoint> ControlPoints = new List<ControlPoint>();
-
-        public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
-        public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
-        public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time);
-
-        public double BPMAt(double time)
-        {
-            return 60000 / BeatLengthAt(time);
-        }
-
-        /// <summary>
-        /// Finds the speed multiplier at a time.
-        /// </summary>
-        /// <param name="time">The time to find the speed multiplier at.</param>
-        /// <returns>The speed multiplier.</returns>
-        public double SpeedMultiplierAt(double time)
-        {
-            ControlPoint overridePoint;
-            ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
-
-            return overridePoint?.SpeedMultiplier ?? timingPoint?.SpeedMultiplier ?? 1;
-        }
-
-        /// <summary>
-        /// Finds the beat length at a time. This is expressed in milliseconds.
-        /// </summary>
-        /// <param name="time">The time to find the beat length at.</param>
-        /// <returns>The beat length.</returns>
-        public double BeatLengthAt(double time)
-        {
-            ControlPoint overridePoint;
-            ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
-
-            return timingPoint.BeatLength;
-        }
-
-        /// <summary>
-        /// Finds the timing point at a time.
-        /// </summary>
-        /// <param name="time">The time to find the timing point at.</param>
-        /// <param name="overridePoint">The timing point containing the velocity change of the returned timing point.</param>
-        /// <returns>The timing point.</returns>
-        public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint)
-        {
-            overridePoint = null;
-
-            ControlPoint timingPoint = null;
-            foreach (var controlPoint in ControlPoints)
-            {
-                // Some beatmaps have the first timingPoint (accidentally) start after the first HitObject(s).
-                // This null check makes it so that the first ControlPoint that makes a timing change is used as
-                // the timingPoint for those HitObject(s).
-                if (controlPoint.Time <= time || timingPoint == null)
-                {
-                    if (controlPoint.TimingChange)
-                    {
-                        timingPoint = controlPoint;
-                        overridePoint = null;
-                    }
-                    else
-                        overridePoint = controlPoint;
-                }
-                else break;
-            }
-
-            return timingPoint ?? new ControlPoint();
-        }
-    }
-}
\ No newline at end of file
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index dfb742f7d1..3489a1550b 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -2,9 +2,11 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
 
 namespace osu.Game.Graphics.Containers
@@ -14,7 +16,8 @@ namespace osu.Game.Graphics.Containers
         private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
 
         private int lastBeat;
-        private ControlPoint lastControlPoint;
+        private TimingControlPoint lastTimingPoint;
+        private EffectControlPoint lastEffectPoint;
 
         protected override void Update()
         {
@@ -22,29 +25,29 @@ namespace osu.Game.Graphics.Containers
                 return;
 
             double currentTrackTime = beatmap.Value.Track.CurrentTime;
-            ControlPoint overridePoint;
-            ControlPoint controlPoint = beatmap.Value.Beatmap.TimingInfo.TimingPointAt(currentTrackTime, out overridePoint);
 
-            if (controlPoint.BeatLength == 0)
+            TimingControlPoint timingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
+            EffectControlPoint effectPoint = beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
+
+            if (timingPoint.BeatLength == 0)
                 return;
 
-            bool kiai = (overridePoint ?? controlPoint).KiaiMode;
-            int beat = (int)((currentTrackTime - controlPoint.Time) / controlPoint.BeatLength);
+            int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
 
             // The beats before the start of the first control point are off by 1, this should do the trick
-            if (currentTrackTime < controlPoint.Time)
-                beat--;
+            if (currentTrackTime < timingPoint.Time)
+                beatIndex--;
 
-            if (controlPoint == lastControlPoint && beat == lastBeat)
+            if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
                 return;
 
-            double offsetFromBeat = (controlPoint.Time - currentTrackTime) % controlPoint.BeatLength;
+            double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
 
             using (BeginDelayedSequence(offsetFromBeat, true))
-                OnNewBeat(beat, controlPoint.BeatLength, controlPoint.TimeSignature, kiai);
+                OnNewBeat(beatIndex, timingPoint, effectPoint, beatmap.Value.Track.CurrentAmplitudes);
 
-            lastBeat = beat;
-            lastControlPoint = controlPoint;
+            lastBeat = beatIndex;
+            lastTimingPoint = timingPoint;
         }
 
         [BackgroundDependencyLoader]
@@ -53,7 +56,7 @@ namespace osu.Game.Graphics.Containers
             beatmap.BindTo(game.Beatmap);
         }
 
-        protected virtual void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
+        protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
         {
         }
     }
diff --git a/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs b/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs
index 5342686c3f..aafa576d4b 100644
--- a/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Beatmaps
             return new Beatmap<T>
             {
                 BeatmapInfo = original.BeatmapInfo,
-                TimingInfo = original.TimingInfo,
+                ControlPointInfo = original.ControlPointInfo,
                 HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList()
             };
         }
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 46fb5fcf70..6028464a46 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Objects.Types;
@@ -33,31 +34,28 @@ namespace osu.Game.Rulesets.Objects
         /// <summary>
         /// Applies default values to this HitObject.
         /// </summary>
+        /// <param name="controlPointInfo">The control points.</param>
         /// <param name="difficulty">The difficulty settings to use.</param>
-        /// <param name="timing">The timing settings to use.</param>
-        public virtual void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
+        public virtual void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
-            ControlPoint overridePoint;
-            ControlPoint timingPoint = timing.TimingPointAt(StartTime, out overridePoint);
-
-            ControlPoint samplePoint = overridePoint ?? timingPoint;
+            SoundControlPoint soundPoint = controlPointInfo.SoundPointAt(StartTime);
 
             // Initialize first sample
-            Samples.ForEach(s => initializeSampleInfo(s, samplePoint));
+            Samples.ForEach(s => initializeSampleInfo(s, soundPoint));
 
             // Initialize any repeat samples
             var repeatData = this as IHasRepeats;
-            repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, samplePoint)));
+            repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, soundPoint)));
         }
 
-        private void initializeSampleInfo(SampleInfo sample, ControlPoint controlPoint)
+        private void initializeSampleInfo(SampleInfo sample, SoundControlPoint soundPoint)
         {
             if (sample.Volume == 0)
-                sample.Volume = controlPoint?.SampleVolume ?? 0;
+                sample.Volume = soundPoint?.SampleVolume ?? 0;
 
             // If the bank is not assigned a name, assign it from the control point
             if (string.IsNullOrEmpty(sample.Bank))
-                sample.Bank = controlPoint?.SampleBank ?? @"normal";
+                sample.Bank = soundPoint?.SampleBank ?? @"normal";
         }
     }
 }
diff --git a/osu.Game/Rulesets/UI/HitRenderer.cs b/osu.Game/Rulesets/UI/HitRenderer.cs
index d0cce87e43..f0dc143668 100644
--- a/osu.Game/Rulesets/UI/HitRenderer.cs
+++ b/osu.Game/Rulesets/UI/HitRenderer.cs
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.UI
 
             // Apply defaults
             foreach (var h in Beatmap.HitObjects)
-                h.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty);
+                h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
 
             // Post-process the beatmap
             processor.PostProcess(Beatmap);
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 3efc85d743..a39e7dbab2 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play
             decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
 
             var firstObjectTime = HitRenderer.Objects.First().StartTime;
-            decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.TimingInfo.BeatLengthAt(firstObjectTime) * 4, Beatmap.BeatmapInfo.AudioLeadIn)));
+            decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, Beatmap.BeatmapInfo.AudioLeadIn)));
             decoupledClock.ProcessFrame();
 
             offsetClock = new FramedOffsetClock(decoupledClock);
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 2f5b35f92a..2d002597ab 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -238,12 +238,12 @@ namespace osu.Game.Screens.Select
 
             private string getBPMRange(Beatmap beatmap)
             {
-                double bpmMax = beatmap.TimingInfo.BPMMaximum;
-                double bpmMin = beatmap.TimingInfo.BPMMinimum;
+                double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
+                double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
 
                 if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm";
 
-                return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.TimingInfo.BPMMode) + "bpm)";
+                return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.ControlPointInfo.BPMMode) + "bpm)";
             }
 
             public class InfoLabel : Container
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 6946462e81..cde7a51ef4 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -87,10 +87,15 @@
     <Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />
     <Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" />
     <Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" />
+    <Compile Include="Beatmaps\ControlPoints\ControlPoint.cs" />
+    <Compile Include="Beatmaps\ControlPoints\ControlPointInfo.cs" />
+    <Compile Include="Beatmaps\ControlPoints\DifficultyControlPoint.cs" />
+    <Compile Include="Beatmaps\ControlPoints\EffectControlPoint.cs" />
+    <Compile Include="Beatmaps\ControlPoints\SoundControlPoint.cs" />
+    <Compile Include="Beatmaps\ControlPoints\TimingControlPoint.cs" />
     <Compile Include="Beatmaps\Legacy\LegacyBeatmap.cs" />
     <Compile Include="Beatmaps\Timing\BreakPeriod.cs" />
     <Compile Include="Beatmaps\Timing\TimeSignatures.cs" />
-    <Compile Include="Beatmaps\Timing\TimingInfo.cs" />
     <Compile Include="Database\BeatmapMetrics.cs" />
     <Compile Include="Database\Database.cs" />
     <Compile Include="Database\RulesetInfo.cs" />
@@ -202,7 +207,6 @@
     <Compile Include="Beatmaps\Drawables\Panel.cs" />
     <Compile Include="Rulesets\Objects\Drawables\DrawableHitObject.cs" />
     <Compile Include="Rulesets\Objects\HitObject.cs" />
-    <Compile Include="Beatmaps\Timing\ControlPoint.cs" />
     <Compile Include="Configuration\OsuConfigManager.cs" />
     <Compile Include="Overlays\Notifications\IHasCompletionTarget.cs" />
     <Compile Include="Overlays\Notifications\Notification.cs" />

From ea4a28532960466c4e8e3c05c80df002275f0ec5 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 14:08:15 +0900
Subject: [PATCH 094/179] Fix compile errors.

---
 .../Patterns/Legacy/HitObjectPatternGenerator.cs     | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index d044ee8893..4547c3d4a3 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -6,6 +6,7 @@ using System.Linq;
 using OpenTK;
 using osu.Game.Audio;
 using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Rulesets.Mania.MathUtils;
 using osu.Game.Rulesets.Mania.Objects;
@@ -25,17 +26,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         {
             StairType = lastStair;
 
-            ControlPoint overridePoint;
-            ControlPoint controlPoint = beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint);
+            TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
+            EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime);
 
             var positionData = hitObject as IHasPosition;
 
             float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length;
             double timeSeparation = hitObject.StartTime - previousTime;
 
-            double beatLength = controlPoint.BeatLength;
-            bool kiai = (overridePoint ?? controlPoint).KiaiMode;
-
             if (timeSeparation <= 125)
             {
                 // More than 120 BPM
@@ -72,12 +70,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 // More than 100 BPM stream
                 convertType |= PatternType.ForceStack | PatternType.LowProbability;
             }
-            else if (positionSeparation < 20 && density >= beatLength / 2.5)
+            else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5)
             {
                 // Low density stream
                 convertType |= PatternType.Reverse | PatternType.LowProbability;
             }
-            else if (density < beatLength / 2.5 || kiai)
+            else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode)
             {
                 // High density
             }

From 6bfd7e0fb0b44a0cd9660211b063416a06a38d9c Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 14:11:37 +0900
Subject: [PATCH 095/179] xmldocs.

---
 osu.Game/Beatmaps/ControlPoints/ControlPoint.cs       |  3 +++
 osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs | 10 ++++++++++
 osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs  | 10 ++++++++++
 osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 10 ++++++++++
 4 files changed, 33 insertions(+)

diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index 5c31259de1..5707caa235 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -5,6 +5,9 @@ namespace osu.Game.Beatmaps.ControlPoints
 {
     public class ControlPoint
     {
+        /// <summary>
+        /// The time at which the control point takes effect.
+        /// </summary>
         public double Time;
     }
 }
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index 2f6003f57c..7671739cd9 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -1,8 +1,18 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
 namespace osu.Game.Beatmaps.ControlPoints
 {
     public class EffectControlPoint : ControlPoint
     {
+        /// <summary>
+        /// Whether this control point enables Kiai mode.
+        /// </summary>
         public bool KiaiMode;
+
+        /// <summary>
+        /// Whether the first bar line of this control point is ignored.
+        /// </summary>
         public bool OmitFirstBarLine;
     }
 }
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs
index 696e684a3d..8084229382 100644
--- a/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs
@@ -1,8 +1,18 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
 namespace osu.Game.Beatmaps.ControlPoints
 {
     public class SoundControlPoint : ControlPoint
     {
+        /// <summary>
+        /// The default sample bank at this control point.
+        /// </summary>
         public string SampleBank;
+
+        /// <summary>
+        /// The default sample volume at this control point.
+        /// </summary>
         public int SampleVolume;
     }
 }
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index 57c21f110b..6f7e38c163 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -1,10 +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 osu.Game.Beatmaps.Timing;
 
 namespace osu.Game.Beatmaps.ControlPoints
 {
     public class TimingControlPoint : ControlPoint
     {
+        /// <summary>
+        /// The time signature at this control point.
+        /// </summary>
         public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
+
+        /// <summary>
+        /// The beat length at this control point.
+        /// </summary>
         public double BeatLength = 500;
     }
 }
\ No newline at end of file

From 002a0e99a2ca7452b815df396e6b9705640f7d34 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 15:20:32 +0900
Subject: [PATCH 096/179] Use SortedList + BinarySearch to find control points
 at time values.

---
 .../Tests/TestCaseGamefield.cs                |  2 +-
 .../UI/ManiaHitRenderer.cs                    | 13 +++--
 .../UI/TaikoHitRenderer.cs                    |  2 +-
 .../Beatmaps/ControlPoints/ControlPoint.cs    |  6 ++-
 .../ControlPoints/ControlPointInfo.cs         | 49 +++++++++++++------
 osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs |  8 +--
 6 files changed, 54 insertions(+), 26 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
index fb5bd8cde4..a44780d673 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
@@ -55,7 +55,7 @@ namespace osu.Desktop.VisualTests.Tests
             }
 
             var controlPointInfo = new ControlPointInfo();
-            controlPointInfo.ControlPoints.Add(new TimingControlPoint
+            controlPointInfo.TimingPoints.Add(new TimingControlPoint
             {
                 BeatLength = 200
             });
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
index 8d5e22933c..8b3623cbab 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
@@ -2,11 +2,13 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using OpenTK;
 using OpenTK.Input;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
+using osu.Framework.Lists;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Beatmaps.Timing;
@@ -38,11 +40,14 @@ namespace osu.Game.Rulesets.Mania.UI
             double lastSpeedMultiplier = 1;
             double lastBeatLength = 500;
 
-            // Generate the timing points, making non-timing changes use the previous timing change
-            var timingChanges = Beatmap.ControlPointInfo.ControlPoints.Where(c => c is TimingControlPoint || c is DifficultyControlPoint).Select(c =>
-            {
-                var change = new TimingChange();
+            // Merge timing + difficulty points
+            var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default);
+            allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints);
+            allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints);
 
+            // Generate the timing points, making non-timing changes use the previous timing change
+            var timingChanges = allPoints.Select(c =>
+            {
                 var timingPoint = c as TimingControlPoint;
                 var difficultyPoint = c as DifficultyControlPoint;
 
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
index 7b0a0b085d..f4d411e15f 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.UI
             TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
             double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
 
-            var timingPoints = Beatmap.ControlPointInfo.ControlPoints.OfType<TimingControlPoint>().ToList();
+            var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
 
             if (timingPoints.Count == 0)
                 return;
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index 5707caa235..0d1dc21e96 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -1,13 +1,17 @@
 // 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;
+
 namespace osu.Game.Beatmaps.ControlPoints
 {
-    public class ControlPoint
+    public class ControlPoint : IComparable<ControlPoint>
     {
         /// <summary>
         /// The time at which the control point takes effect.
         /// </summary>
         public double Time;
+
+        public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
     }
 }
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 90c50a3e05..84b6dcfc20 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -1,63 +1,82 @@
 using System.Collections.Generic;
 using System.Linq;
+using osu.Framework.Lists;
 
 namespace osu.Game.Beatmaps.ControlPoints
 {
     public class ControlPointInfo
     {
-        /// <summary>
-        /// All the control points.
-        /// </summary>
-        public readonly List<ControlPoint> ControlPoints = new List<ControlPoint>();
+        public readonly SortedList<TimingControlPoint> TimingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
+        public readonly SortedList<DifficultyControlPoint> DifficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
+        public readonly SortedList<SoundControlPoint> SoundPoints = new SortedList<SoundControlPoint>(Comparer<SoundControlPoint>.Default);
+        public readonly SortedList<EffectControlPoint> EffectPoints = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default);
 
         /// <summary>
         /// Finds the difficulty control point that is active at <paramref name="time"/>.
         /// </summary>
         /// <param name="time">The time to find the difficulty control point at.</param>
         /// <returns>The difficulty control point.</returns>
-        public DifficultyControlPoint DifficultyPointAt(double time) =>
-            ControlPoints.OfType<DifficultyControlPoint>().LastOrDefault(t => t.Time <= time) ?? new DifficultyControlPoint();
+        public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time);
 
         /// <summary>
         /// Finds the effect control point that is active at <paramref name="time"/>.
         /// </summary>
         /// <param name="time">The time to find the effect control point at.</param>
         /// <returns>The effect control point.</returns>
-        public EffectControlPoint EffectPointAt(double time) =>
-            ControlPoints.OfType<EffectControlPoint>().LastOrDefault(t => t.Time <= time) ?? new EffectControlPoint();
+        public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time);
 
         /// <summary>
         /// Finds the sound control point that is active at <paramref name="time"/>.
         /// </summary>
         /// <param name="time">The time to find the sound control point at.</param>
         /// <returns>The sound control point.</returns>
-        public SoundControlPoint SoundPointAt(double time) =>
-            ControlPoints.OfType<SoundControlPoint>().LastOrDefault(t => t.Time <= time) ?? new SoundControlPoint();
+        public SoundControlPoint SoundPointAt(double time) => binarySearch(SoundPoints, time);
 
         /// <summary>
         /// Finds the timing control point that is active at <paramref name="time"/>.
         /// </summary>
         /// <param name="time">The time to find the timing control point at.</param>
         /// <returns>The timing control point.</returns>
-        public TimingControlPoint TimingPointAt(double time) =>
-            ControlPoints.OfType<TimingControlPoint>().LastOrDefault(t => t.Time <= time) ?? new TimingControlPoint();
+        public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time);
 
         /// <summary>
         /// Finds the maximum BPM represented by any timing control point.
         /// </summary>
         public double BPMMaximum =>
-            60000 / (ControlPoints.OfType<TimingControlPoint>().OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+            60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
 
         /// <summary>
         /// Finds the minimum BPM represented by any timing control point.
         /// </summary>
         public double BPMMinimum =>
-            60000 / (ControlPoints.OfType<TimingControlPoint>().OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+            60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
 
         /// <summary>
         /// Finds the mode BPM (most common BPM) represented by the control points.
         /// </summary>
         public double BPMMode =>
-            60000 / (ControlPoints.OfType<TimingControlPoint>().GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+            60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+
+        private T binarySearch<T>(SortedList<T> list, double time)
+            where T : ControlPoint, new()
+        {
+            if (list.Count == 0)
+                return new T();
+
+            if (time < list[0].Time)
+                return new T();
+
+            int index = list.BinarySearch(new T() { Time = time });
+
+            // Check if we've found an exact match (t == time)
+            if (index >= 0)
+                return list[index];
+
+            index = ~index;
+
+            if (index == list.Count)
+                return list[list.Count - 1];
+            return list[index - 1];
+        }
     }
 }
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
index e140e2282c..875efab5f9 100644
--- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
@@ -285,7 +285,7 @@ namespace osu.Game.Beatmaps.Formats
 
             if (timingChange && (beatLength != timingPoint.BeatLength || timeSignature != timingPoint.TimeSignature))
             {
-                beatmap.ControlPointInfo.ControlPoints.Add(new TimingControlPoint
+                beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
                 {
                     Time = time,
                     BeatLength = beatLength,
@@ -295,7 +295,7 @@ namespace osu.Game.Beatmaps.Formats
 
             if (speedMultiplier != difficultyPoint.SpeedMultiplier)
             {
-                beatmap.ControlPointInfo.ControlPoints.Add(new DifficultyControlPoint
+                beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
                 {
                     Time = time,
                     SpeedMultiplier = speedMultiplier
@@ -304,7 +304,7 @@ namespace osu.Game.Beatmaps.Formats
 
             if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
             {
-                beatmap.ControlPointInfo.ControlPoints.Add(new SoundControlPoint
+                beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
                 {
                     Time = time,
                     SampleBank = stringSampleSet,
@@ -314,7 +314,7 @@ namespace osu.Game.Beatmaps.Formats
 
             if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
             {
-                beatmap.ControlPointInfo.ControlPoints.Add(new EffectControlPoint
+                beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
                 {
                     Time = time,
                     KiaiMode = kiaiMode,

From 2344f37a3c337f0b6d2f071c7c6eadc38f05e766 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 15:26:07 +0900
Subject: [PATCH 097/179] Always add TimingChange control points.

---
 osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
index 875efab5f9..448b6b6b98 100644
--- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
@@ -278,12 +278,11 @@ namespace osu.Game.Beatmaps.Formats
             if (stringSampleSet == @"none")
                 stringSampleSet = @"normal";
 
-            TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
             DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
             SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
             EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
 
-            if (timingChange && (beatLength != timingPoint.BeatLength || timeSignature != timingPoint.TimeSignature))
+            if (timingChange)
             {
                 beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
                 {

From 836bf930a0be6e473578d0ead265b3f4f0cfe0a1 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 15:29:38 +0900
Subject: [PATCH 098/179] More cleanups.

---
 .../Tests/TestCaseGamefield.cs                 |  1 -
 .../Tests/TestCaseManiaPlayfield.cs            |  2 --
 .../Legacy/DistanceObjectPatternGenerator.cs   |  1 -
 .../Legacy/HitObjectPatternGenerator.cs        |  1 -
 osu.Game.Rulesets.Mania/Objects/HoldNote.cs    |  1 -
 osu.Game.Rulesets.Mania/Objects/Note.cs        |  1 -
 .../Timing/ControlPointContainer.cs            |  1 -
 osu.Game.Rulesets.Mania/UI/Column.cs           |  2 --
 osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs |  1 -
 osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs   |  2 --
 osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs  |  1 -
 osu.Game.Rulesets.Osu/Objects/Slider.cs        |  1 -
 osu.Game.Rulesets.Osu/Objects/Spinner.cs       |  1 -
 osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs    |  1 -
 osu.Game.Rulesets.Taiko/Objects/Hit.cs         |  1 -
 .../Objects/TaikoHitObject.cs                  |  1 -
 osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs |  1 -
 .../Beatmaps/ControlPoints/ControlPointInfo.cs | 18 ++++++++++++++++++
 .../ControlPoints/DifficultyControlPoint.cs    |  6 ++++++
 osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs  |  1 -
 .../Graphics/Containers/BeatSyncedContainer.cs |  1 -
 osu.Game/Rulesets/Objects/HitObject.cs         |  1 -
 22 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
index a44780d673..e2cd2bf67b 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs
@@ -18,7 +18,6 @@ using osu.Game.Rulesets.Taiko.UI;
 using System.Collections.Generic;
 using osu.Desktop.VisualTests.Beatmaps;
 using osu.Framework.Allocation;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Desktop.VisualTests.Tests
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
index 88f90fc333..ec50f19f98 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
@@ -7,11 +7,9 @@ using osu.Framework.Graphics;
 using osu.Game.Rulesets.Mania.UI;
 using System;
 using System.Collections.Generic;
-using osu.Game.Beatmaps.Timing;
 using OpenTK;
 using osu.Game.Rulesets.Mania.Objects.Drawables;
 using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Rulesets.Mania.Timing;
 
 namespace osu.Desktop.VisualTests.Tests
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 1209a5c879..718e0967da 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -5,7 +5,6 @@ using System;
 using System.Linq;
 using osu.Game.Audio;
 using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Rulesets.Mania.MathUtils;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index 4547c3d4a3..b1ba99d98b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -7,7 +7,6 @@ using OpenTK;
 using osu.Game.Audio;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Rulesets.Mania.MathUtils;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Objects;
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index b0a0ed10b8..30e71aeb5d 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -3,7 +3,6 @@
 
 using osu.Game.Audio;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Mania.Judgements;
 using osu.Game.Rulesets.Objects.Types;
diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs
index e91c00c145..6c0cacd277 100644
--- a/osu.Game.Rulesets.Mania/Objects/Note.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Note.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Mania.Judgements;
 
diff --git a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
index 38e975f468..6d390464fe 100644
--- a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
+++ b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
@@ -7,7 +7,6 @@ using System.Linq;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using OpenTK;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Mania.Timing
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 12c4cea58c..c8cb5f6387 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -17,8 +17,6 @@ using System.Collections.Generic;
 using osu.Game.Rulesets.Objects.Drawables;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mania.Judgements;
-using osu.Game.Beatmaps.Timing;
-using osu.Game.Beatmaps.ControlPoints;
 using System;
 using osu.Framework.Configuration;
 
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
index 8b3623cbab..95b7979e43 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
@@ -11,7 +11,6 @@ using osu.Framework.Graphics;
 using osu.Framework.Lists;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Rulesets.Beatmaps;
 using osu.Game.Rulesets.Mania.Beatmaps;
 using osu.Game.Rulesets.Mania.Judgements;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index c0915c5d82..ff763f87c4 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -19,10 +19,8 @@ using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Game.Rulesets.Objects.Drawables;
 using osu.Game.Rulesets.Mania.Timing;
 using osu.Framework.Input;
-using osu.Game.Beatmaps.Timing;
 using osu.Framework.Graphics.Transforms;
 using osu.Framework.MathUtils;
-using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Mania.UI
 {
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index c4b4e3fc4a..e6fd82e6c8 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -6,7 +6,6 @@ using OpenTK;
 using osu.Game.Rulesets.Osu.Objects.Drawables;
 using osu.Game.Rulesets.Objects.Types;
 using OpenTK.Graphics;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Beatmaps.ControlPoints;
 
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 1a5065d17d..3b44e38d5e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Rulesets.Objects.Types;
 using System;
 using System.Collections.Generic;
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 8c0a218108..eff60ba935 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Beatmaps.ControlPoints;
 
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 857dd8d3a5..18e3016fc3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -5,7 +5,6 @@ using osu.Game.Rulesets.Objects.Types;
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Audio;
 using osu.Game.Beatmaps.ControlPoints;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
index cc15b5ab98..f31472d0fd 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 
 namespace osu.Game.Rulesets.Taiko.Objects
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index a294a13521..4f87467706 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Taiko.UI;
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
index f4d411e15f..662cace511 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs
@@ -18,7 +18,6 @@ using osu.Game.Rulesets.Taiko.Replays;
 using OpenTK;
 using osu.Game.Rulesets.Beatmaps;
 using System.Linq;
-using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Rulesets.Taiko.UI
 {
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 84b6dcfc20..5740c961b1 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -1,3 +1,6 @@
+// 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.Collections.Generic;
 using System.Linq;
 using osu.Framework.Lists;
@@ -6,9 +9,24 @@ namespace osu.Game.Beatmaps.ControlPoints
 {
     public class ControlPointInfo
     {
+        /// <summary>
+        /// All timing points.
+        /// </summary>
         public readonly SortedList<TimingControlPoint> TimingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
+
+        /// <summary>
+        /// All difficulty points.
+        /// </summary>
         public readonly SortedList<DifficultyControlPoint> DifficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
+
+        /// <summary>
+        /// All sound points.
+        /// </summary>
         public readonly SortedList<SoundControlPoint> SoundPoints = new SortedList<SoundControlPoint>(Comparer<SoundControlPoint>.Default);
+
+        /// <summary>
+        /// All effect points.
+        /// </summary>
         public readonly SortedList<EffectControlPoint> EffectPoints = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default);
 
         /// <summary>
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index abd9d05971..30c7884334 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -1,7 +1,13 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
 namespace osu.Game.Beatmaps.ControlPoints
 {
     public class DifficultyControlPoint : ControlPoint
     {
+        /// <summary>
+        /// The speed multiplier at this control point.
+        /// </summary>
         public double SpeedMultiplier = 1;
     }
 }
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
index 448b6b6b98..cb1d9d2fcd 100644
--- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
@@ -8,7 +8,6 @@ using OpenTK.Graphics;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Objects.Legacy;
-using System.Linq;
 using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Beatmaps.Formats
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 3489a1550b..429a794a03 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -7,7 +7,6 @@ using osu.Framework.Configuration;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 
 namespace osu.Game.Graphics.Containers
 {
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 6028464a46..5592681cab 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -3,7 +3,6 @@
 
 using osu.Game.Audio;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Objects.Types;
 

From 1867cbb3817439813c8d33c1e5680b8175d86026 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 16:05:58 +0900
Subject: [PATCH 099/179] Revert a bit of BeatSyncedContainer for smaller
 changeset.

---
 osu-framework                                     |  2 +-
 .../Graphics/Containers/BeatSyncedContainer.cs    | 15 +++++++--------
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/osu-framework b/osu-framework
index 773d60eb6b..c6f030d6f1 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 773d60eb6b811f395e32a22dc66bb4d2e63a6dbc
+Subproject commit c6f030d6f1ab65a48de9ff1d0c424acb686e0149
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 429a794a03..5895eabde4 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -2,11 +2,11 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Framework.Allocation;
-using osu.Framework.Audio.Track;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
 
 namespace osu.Game.Graphics.Containers
 {
@@ -16,7 +16,6 @@ namespace osu.Game.Graphics.Containers
 
         private int lastBeat;
         private TimingControlPoint lastTimingPoint;
-        private EffectControlPoint lastEffectPoint;
 
         protected override void Update()
         {
@@ -31,21 +30,21 @@ namespace osu.Game.Graphics.Containers
             if (timingPoint.BeatLength == 0)
                 return;
 
-            int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
+            int beat = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
 
             // The beats before the start of the first control point are off by 1, this should do the trick
             if (currentTrackTime < timingPoint.Time)
-                beatIndex--;
+                beat--;
 
-            if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
+            if (timingPoint == lastTimingPoint && beat == lastBeat)
                 return;
 
             double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
 
             using (BeginDelayedSequence(offsetFromBeat, true))
-                OnNewBeat(beatIndex, timingPoint, effectPoint, beatmap.Value.Track.CurrentAmplitudes);
+                OnNewBeat(beat, timingPoint.BeatLength, timingPoint.TimeSignature, effectPoint.KiaiMode);
 
-            lastBeat = beatIndex;
+            lastBeat = beat;
             lastTimingPoint = timingPoint;
         }
 
@@ -55,7 +54,7 @@ namespace osu.Game.Graphics.Containers
             beatmap.BindTo(game.Beatmap);
         }
 
-        protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
+        protected virtual void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
         {
         }
     }

From aad6f8f5d61f05f752f06932643ce186099db155 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 16:11:46 +0900
Subject: [PATCH 100/179] Refactoring of BeatSyncedContainer.

---
 osu-framework                                      |  2 +-
 .../Graphics/Containers/BeatSyncedContainer.cs     | 14 +++++++-------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/osu-framework b/osu-framework
index c6f030d6f1..773d60eb6b 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit c6f030d6f1ab65a48de9ff1d0c424acb686e0149
+Subproject commit 773d60eb6b811f395e32a22dc66bb4d2e63a6dbc
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 5895eabde4..3d08431bea 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -2,11 +2,11 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 
 namespace osu.Game.Graphics.Containers
 {
@@ -30,21 +30,21 @@ namespace osu.Game.Graphics.Containers
             if (timingPoint.BeatLength == 0)
                 return;
 
-            int beat = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
+            int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
 
             // The beats before the start of the first control point are off by 1, this should do the trick
             if (currentTrackTime < timingPoint.Time)
-                beat--;
+                beatIndex--;
 
-            if (timingPoint == lastTimingPoint && beat == lastBeat)
+            if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
                 return;
 
             double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
 
             using (BeginDelayedSequence(offsetFromBeat, true))
-                OnNewBeat(beat, timingPoint.BeatLength, timingPoint.TimeSignature, effectPoint.KiaiMode);
+                OnNewBeat(beatIndex, timingPoint, effectPoint, beatmap.Value.Track.CurrentAmplitudes);
 
-            lastBeat = beat;
+            lastBeat = beatIndex;
             lastTimingPoint = timingPoint;
         }
 
@@ -54,7 +54,7 @@ namespace osu.Game.Graphics.Containers
             beatmap.BindTo(game.Beatmap);
         }
 
-        protected virtual void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
+        protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
         {
         }
     }

From 7e5bb61a44daf7ce3e57de1bf5d476830b560d52 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 16:13:51 +0900
Subject: [PATCH 101/179] Fix line endings.

---
 .../Timing/TimingChange.cs                    | 44 +++++++++----------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Timing/TimingChange.cs b/osu.Game.Rulesets.Mania/Timing/TimingChange.cs
index fb6f1d2db1..9153ba6991 100644
--- a/osu.Game.Rulesets.Mania/Timing/TimingChange.cs
+++ b/osu.Game.Rulesets.Mania/Timing/TimingChange.cs
@@ -1,23 +1,23 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-namespace osu.Game.Rulesets.Mania.Timing
-{
-    public class TimingChange
-    {
-        /// <summary>
-        /// The time at which this timing change happened.
-        /// </summary>
-        public double Time;
-
-        /// <summary>
-        /// The beat length.
-        /// </summary>
-        public double BeatLength = 500;
-
-        /// <summary>
-        /// The speed multiplier.
-        /// </summary>
-        public double SpeedMultiplier = 1;
-    }
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Rulesets.Mania.Timing
+{
+    public class TimingChange
+    {
+        /// <summary>
+        /// The time at which this timing change happened.
+        /// </summary>
+        public double Time;
+
+        /// <summary>
+        /// The beat length.
+        /// </summary>
+        public double BeatLength = 500;
+
+        /// <summary>
+        /// The speed multiplier.
+        /// </summary>
+        public double SpeedMultiplier = 1;
+    }
 }
\ No newline at end of file

From fe7ac20e292cbd014a1d6d15a41f3756da83c008 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 23 May 2017 16:26:51 +0900
Subject: [PATCH 102/179] Read menu music from osz resource

---
 osu-resources                                 |   2 +-
 .../Beatmaps/IO/LegacyFilesystemReader.cs     |   2 +
 osu.Game/Beatmaps/IO/ArchiveReader.cs         |   2 +
 osu.Game/Beatmaps/IO/OszArchiveReader.cs      |   2 +
 osu.Game/Database/BeatmapDatabase.cs          | 113 +++++++++---------
 osu.Game/OsuGame.cs                           |   2 +-
 osu.Game/Screens/Menu/Intro.cs                |  49 +++++++-
 osu.Game/Screens/Menu/MainMenu.cs             |  32 +----
 8 files changed, 111 insertions(+), 93 deletions(-)

diff --git a/osu-resources b/osu-resources
index ffccbeb98d..9f46a456dc 160000
--- a/osu-resources
+++ b/osu-resources
@@ -1 +1 @@
-Subproject commit ffccbeb98dc9e8f0965520270b5885e63f244c83
+Subproject commit 9f46a456dc3a56dcbff09671a3f588b16a464106
diff --git a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs
index 8c896646bf..8772fc9f28 100644
--- a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs
+++ b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs
@@ -37,5 +37,7 @@ namespace osu.Desktop.Beatmaps.IO
         {
             // no-op
         }
+
+        public override Stream GetUnderlyingStream() => null;
     }
 }
diff --git a/osu.Game/Beatmaps/IO/ArchiveReader.cs b/osu.Game/Beatmaps/IO/ArchiveReader.cs
index 9d7d67007e..7ff5668b6f 100644
--- a/osu.Game/Beatmaps/IO/ArchiveReader.cs
+++ b/osu.Game/Beatmaps/IO/ArchiveReader.cs
@@ -63,5 +63,7 @@ namespace osu.Game.Beatmaps.IO
                 return buffer;
             }
         }
+
+        public abstract Stream GetUnderlyingStream();
     }
 }
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/IO/OszArchiveReader.cs b/osu.Game/Beatmaps/IO/OszArchiveReader.cs
index 6c550def8d..eb9e740779 100644
--- a/osu.Game/Beatmaps/IO/OszArchiveReader.cs
+++ b/osu.Game/Beatmaps/IO/OszArchiveReader.cs
@@ -49,5 +49,7 @@ namespace osu.Game.Beatmaps.IO
             archive.Dispose();
             archiveStream.Dispose();
         }
+
+        public override Stream GetUnderlyingStream() => archiveStream;
     }
 }
\ No newline at end of file
diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs
index bd25ebe8a9..efd5631077 100644
--- a/osu.Game/Database/BeatmapDatabase.cs
+++ b/osu.Game/Database/BeatmapDatabase.cs
@@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Formats;
 using osu.Game.Beatmaps.IO;
 using osu.Game.IPC;
+using osu.Game.Screens.Menu;
 using SQLite.Net;
 using SQLiteNetExtensions.Extensions;
 
@@ -38,6 +39,10 @@ namespace osu.Game.Database
         {
             foreach (var b in GetAllWithChildren<BeatmapSetInfo>(b => b.DeletePending))
             {
+                if (b.Hash == Intro.MENU_MUSIC_BEATMAP_HASH)
+                    // this is a bit hacky, but will do for now.
+                    continue;
+
                 try
                 {
                     Storage.Delete(b.Path);
@@ -97,50 +102,49 @@ namespace osu.Game.Database
             typeof(BeatmapDifficulty),
         };
 
+        public void Import(string path)
+        {
+            try
+            {
+                Import(ArchiveReader.GetReader(Storage, path));
+
+                // We may or may not want to delete the file depending on where it is stored.
+                //  e.g. reconstructing/repairing database with beatmaps from default storage.
+                // Also, not always a single file, i.e. for LegacyFilesystemReader
+                // TODO: Add a check to prevent files from storage to be deleted.
+                try
+                {
+                    File.Delete(path);
+                }
+                catch (Exception e)
+                {
+                    Logger.Error(e, $@"Could not delete file at {path}");
+                }
+            }
+            catch (Exception e)
+            {
+                e = e.InnerException ?? e;
+                Logger.Error(e, @"Could not import beatmap set");
+            }
+        }
+
+        public void Import(ArchiveReader archiveReader)
+        {
+            BeatmapSetInfo set = getBeatmapSet(archiveReader);
+
+            //If we have an ID then we already exist in the database.
+            if (set.ID == 0)
+                Import(new[] { set });
+        }
+
         /// <summary>
         /// Import multiple <see cref="BeatmapSetInfo"/> from <paramref name="paths"/>.
         /// </summary>
         /// <param name="paths">Multiple locations on disk</param>
-        public void Import(IEnumerable<string> paths)
+        public void Import(params string[] paths)
         {
             foreach (string p in paths)
-            {
-                try
-                {
-                    BeatmapSetInfo set = getBeatmapSet(p);
-
-                    //If we have an ID then we already exist in the database.
-                    if (set.ID == 0)
-                        Import(new[] { set });
-
-                    // We may or may not want to delete the file depending on where it is stored.
-                    //  e.g. reconstructing/repairing database with beatmaps from default storage.
-                    // Also, not always a single file, i.e. for LegacyFilesystemReader
-                    // TODO: Add a check to prevent files from storage to be deleted.
-                    try
-                    {
-                        File.Delete(p);
-                    }
-                    catch (Exception e)
-                    {
-                        Logger.Error(e, $@"Could not delete file at {p}");
-                    }
-                }
-                catch (Exception e)
-                {
-                    e = e.InnerException ?? e;
-                    Logger.Error(e, @"Could not import beatmap set");
-                }
-            }
-        }
-
-        /// <summary>
-        /// Import <see cref="BeatmapSetInfo"/> from <paramref name="path"/>.
-        /// </summary>
-        /// <param name="path">Location on disk</param>
-        public void Import(string path)
-        {
-            Import(new[] { path });
+                Import(p);
         }
 
         /// <summary>
@@ -148,29 +152,26 @@ namespace osu.Game.Database
         /// </summary>
         /// <param name="path">Content location</param>
         /// <returns><see cref="BeatmapSetInfo"/></returns>
-        private BeatmapSetInfo getBeatmapSet(string path)
-        {
-            string hash = null;
+        private BeatmapSetInfo getBeatmapSet(string path) => getBeatmapSet(ArchiveReader.GetReader(Storage, path));
 
+        private BeatmapSetInfo getBeatmapSet(ArchiveReader archiveReader)
+        {
             BeatmapMetadata metadata;
 
-            using (var reader = ArchiveReader.GetReader(Storage, path))
-            {
-                using (var stream = new StreamReader(reader.GetStream(reader.BeatmapFilenames[0])))
-                    metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
-            }
+            using (var stream = new StreamReader(archiveReader.GetStream(archiveReader.BeatmapFilenames[0])))
+                metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
 
-            if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader
+            string hash;
+            string path;
+
+            using (var input = archiveReader.GetUnderlyingStream())
             {
-                using (var input = Storage.GetStream(path))
-                {
-                    hash = input.GetMd5Hash();
-                    input.Seek(0, SeekOrigin.Begin);
-                    path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
-                    if (!Storage.Exists(path))
-                        using (var output = Storage.GetStream(path, FileAccess.Write))
-                            input.CopyTo(output);
-                }
+                hash = input.GetMd5Hash();
+                input.Seek(0, SeekOrigin.Begin);
+                path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
+                if (!Storage.Exists(path))
+                    using (var output = Storage.GetStream(path, FileAccess.Write))
+                        input.CopyTo(output);
             }
 
             var existing = Connection.Table<BeatmapSetInfo>().FirstOrDefault(b => b.Hash == hash);
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 4cf436a8e4..f13043a745 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -82,7 +82,7 @@ namespace osu.Game
             if (args?.Length > 0)
             {
                 var paths = args.Where(a => !a.StartsWith(@"-"));
-                Task.Run(() => BeatmapDatabase.Import(paths));
+                Task.Run(() => BeatmapDatabase.Import(paths.ToArray()));
             }
 
             Dependencies.Cache(this);
diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs
index 01659edd72..5791b7f196 100644
--- a/osu.Game/Screens/Menu/Intro.cs
+++ b/osu.Game/Screens/Menu/Intro.cs
@@ -8,7 +8,10 @@ using osu.Framework.Audio.Track;
 using osu.Framework.Configuration;
 using osu.Framework.Screens;
 using osu.Framework.Graphics;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps.IO;
 using osu.Game.Configuration;
+using osu.Game.Database;
 using osu.Game.Graphics.Containers;
 using osu.Game.Screens.Backgrounds;
 using OpenTK.Graphics;
@@ -19,6 +22,8 @@ namespace osu.Game.Screens.Menu
     {
         private readonly OsuLogo logo;
 
+        public const string MENU_MUSIC_BEATMAP_HASH = "21c1271b91234385978b5418881fdd88";
+
         /// <summary>
         /// Whether we have loaded the menu previously.
         /// </summary>
@@ -27,7 +32,6 @@ namespace osu.Game.Screens.Menu
         private MainMenu mainMenu;
         private SampleChannel welcome;
         private SampleChannel seeya;
-        private Track bgm;
 
         internal override bool HasLocalCursorDisplayed => true;
 
@@ -60,15 +64,49 @@ namespace osu.Game.Screens.Menu
 
         private Bindable<bool> menuVoice;
         private Bindable<bool> menuMusic;
+        private Track track;
 
         [BackgroundDependencyLoader]
-        private void load(AudioManager audio, OsuConfigManager config)
+        private void load(AudioManager audio, OsuConfigManager config, BeatmapDatabase beatmaps, Framework.Game game)
         {
             menuVoice = config.GetBindable<bool>(OsuSetting.MenuVoice);
             menuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic);
 
-            bgm = audio.Track.Get(@"circles");
-            bgm.Looping = true;
+            var trackManager = audio.Track;
+
+            BeatmapSetInfo setInfo = null;
+
+            if (!menuMusic)
+            {
+                var query = beatmaps.Query<BeatmapSetInfo>().Where(b => !b.DeletePending);
+                int count = query.Count();
+                if (count > 0)
+                    setInfo = query.ElementAt(RNG.Next(0, count - 1));
+            }
+
+            if (setInfo == null)
+            {
+                var query = beatmaps.Query<BeatmapSetInfo>().Where(b => b.Hash == MENU_MUSIC_BEATMAP_HASH);
+
+                setInfo = query.FirstOrDefault();
+
+                if (setInfo == null)
+                {
+                    // we need to import the default menu background beatmap
+                    beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz")));
+
+                    setInfo = query.First();
+
+                    setInfo.DeletePending = true;
+                    beatmaps.Update(setInfo, false);
+                }
+            }
+
+            beatmaps.GetChildren(setInfo);
+            Beatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
+
+            track = Beatmap.Track;
+            trackManager.SetExclusive(track);
 
             welcome = audio.Sample.Get(@"welcome");
             seeya = audio.Sample.Get(@"seeya");
@@ -83,8 +121,7 @@ namespace osu.Game.Screens.Menu
 
             Scheduler.AddDelayed(delegate
             {
-                if (menuMusic)
-                    bgm.Start();
+                track.Start();
 
                 LoadComponentAsync(mainMenu = new MainMenu());
 
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 71d020b0f2..b74ee52f9c 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -1,18 +1,12 @@
 // 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.Threading.Tasks;
 using OpenTK;
 using OpenTK.Input;
 using osu.Framework.Allocation;
-using osu.Framework.Audio.Track;
-using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Input;
-using osu.Framework.MathUtils;
 using osu.Framework.Screens;
-using osu.Game.Configuration;
-using osu.Game.Database;
 using osu.Game.Graphics.Containers;
 using osu.Game.Screens.Backgrounds;
 using osu.Game.Screens.Charts;
@@ -60,30 +54,11 @@ namespace osu.Game.Screens.Menu
             };
         }
 
-        private Bindable<bool> menuMusic;
-        private TrackManager trackManager;
-
         [BackgroundDependencyLoader]
-        private void load(OsuGame game, OsuConfigManager config, BeatmapDatabase beatmaps)
+        private void load(OsuGame game)
         {
-            menuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic);
             LoadComponentAsync(background);
 
-            if (!menuMusic)
-            {
-                trackManager = game.Audio.Track;
-
-                var query = beatmaps.Query<BeatmapSetInfo>().Where(b => !b.DeletePending);
-                int count = query.Count();
-
-                if (count > 0)
-                {
-                    var beatmap = query.ElementAt(RNG.Next(0, count - 1));
-                    beatmaps.GetChildren(beatmap);
-                    Beatmap = beatmaps.GetWorkingBeatmap(beatmap.Beatmaps[0]);
-                }
-            }
-
             buttons.OnSettings = game.ToggleSettings;
 
             preloadSongSelect();
@@ -108,14 +83,13 @@ namespace osu.Game.Screens.Menu
             buttons.FadeInFromZero(500);
             if (last is Intro && Beatmap != null)
             {
-                Task.Run(() =>
+                if (!Beatmap.Track.IsRunning)
                 {
-                    trackManager.SetExclusive(Beatmap.Track);
                     Beatmap.Track.Seek(Beatmap.Metadata.PreviewTime);
                     if (Beatmap.Metadata.PreviewTime == -1)
                         Beatmap.Track.Seek(Beatmap.Track.Length * 0.4f);
                     Beatmap.Track.Start();
-                });
+                }
             }
         }
 

From 61348ff08d7269f1e4752a79d00f9e81e34ab7a0 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 16:42:17 +0900
Subject: [PATCH 103/179] Restructure playfield so that various elements are
 masked.

---
 osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 49 +++++++++++---------
 1 file changed, 27 insertions(+), 22 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index d1d895fc1d..e6677ff0cd 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -44,10 +44,12 @@ namespace osu.Game.Rulesets.Taiko.UI
 
         private readonly Container hitObjectContainer;
         private readonly Container topLevelHitContainer;
-        private readonly Container leftBackgroundContainer;
-        private readonly Container rightBackgroundContainer;
-        private readonly Box leftBackground;
-        private readonly Box rightBackground;
+
+        private readonly Container overlayBackgroundContainer;
+        private readonly Container backgroundContainer;
+
+        private readonly Box overlayBackground;
+        private readonly Box background;
 
         public TaikoPlayfield()
         {
@@ -59,7 +61,7 @@ namespace osu.Game.Rulesets.Taiko.UI
                     Height = DEFAULT_PLAYFIELD_HEIGHT,
                     Children = new[]
                     {
-                        rightBackgroundContainer = new Container
+                        backgroundContainer = new Container
                         {
                             Name = "Transparent playfield background",
                             RelativeSizeAxes = Axes.Both,
@@ -73,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.UI
                             },
                             Children = new Drawable[]
                             {
-                                rightBackground = new Box
+                                background = new Box
                                 {
                                     RelativeSizeAxes = Axes.Both,
                                     Alpha = 0.6f
@@ -82,16 +84,17 @@ namespace osu.Game.Rulesets.Taiko.UI
                         },
                         new Container
                         {
-                            Name = "Transparent playfield elements",
+                            Name = "Right area",
                             RelativeSizeAxes = Axes.Both,
-                            Padding = new MarginPadding { Left = left_area_size },
+                            Margin = new MarginPadding { Left = left_area_size },
                             Children = new Drawable[]
                             {
                                 new Container
                                 {
-                                    Name = "Hit target container",
-                                    X = hit_target_offset,
+                                    Name = "Masked elements",
                                     RelativeSizeAxes = Axes.Both,
+                                    Padding = new MarginPadding { Left = hit_target_offset },
+                                    Masking = true,
                                     Children = new Drawable[]
                                     {
                                         hitExplosionContainer = new Container<HitExplosion>
@@ -114,23 +117,25 @@ namespace osu.Game.Rulesets.Taiko.UI
                                         {
                                             RelativeSizeAxes = Axes.Both,
                                         },
-                                        judgementContainer = new Container<DrawableTaikoJudgement>
-                                        {
-                                            RelativeSizeAxes = Axes.Y,
-                                            BlendingMode = BlendingMode.Additive
-                                        },
-                                    },
+                                    }
+                                },
+                                judgementContainer = new Container<DrawableTaikoJudgement>
+                                {
+                                    Name = "Judgements",
+                                    RelativeSizeAxes = Axes.Y,
+                                    Margin = new MarginPadding { Left = hit_target_offset },
+                                    BlendingMode = BlendingMode.Additive
                                 },
                             }
                         },
-                        leftBackgroundContainer = new Container
+                        overlayBackgroundContainer = new Container
                         {
                             Name = "Left overlay",
                             Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT),
                             BorderThickness = 1,
                             Children = new Drawable[]
                             {
-                                leftBackground = new Box
+                                overlayBackground = new Box
                                 {
                                     RelativeSizeAxes = Axes.Both,
                                 },
@@ -164,11 +169,11 @@ namespace osu.Game.Rulesets.Taiko.UI
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-            leftBackgroundContainer.BorderColour = colours.Gray0;
-            leftBackground.Colour = colours.Gray1;
+            overlayBackgroundContainer.BorderColour = colours.Gray0;
+            overlayBackground.Colour = colours.Gray1;
 
-            rightBackgroundContainer.BorderColour = colours.Gray1;
-            rightBackground.Colour = colours.Gray0;
+            backgroundContainer.BorderColour = colours.Gray1;
+            background.Colour = colours.Gray0;
         }
 
         public override void Add(DrawableHitObject<TaikoHitObject, TaikoJudgement> h)

From 73320f9a7ea8acd293830ee2af1053efc418f948 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 23 May 2017 15:29:23 +0900
Subject: [PATCH 104/179] Don't bounce the ripple

Also ripple better.
---
 osu.Game/Screens/Menu/OsuLogo.cs | 57 ++++++++++++++++----------------
 1 file changed, 29 insertions(+), 28 deletions(-)

diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 5f9a3bf655..738204e30e 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Screens.Menu
 
             Children = new Drawable[]
             {
-                logoBeatContainer  = new Container
+                logoHoverContainer = new Container
                 {
                     AutoSizeAxes = Axes.Both,
                     Children = new Drawable[]
@@ -95,7 +95,23 @@ namespace osu.Game.Screens.Menu
                             AutoSizeAxes = Axes.Both,
                             Children = new Drawable[]
                             {
-                                logoHoverContainer = new Container
+                                rippleContainer = new Container
+                                {
+                                    Anchor = Anchor.Centre,
+                                    Origin = Anchor.Centre,
+                                    RelativeSizeAxes = Axes.Both,
+                                    Children = new Drawable[]
+                                    {
+                                        ripple = new Sprite
+                                        {
+                                            Anchor = Anchor.Centre,
+                                            Origin = Anchor.Centre,
+                                            BlendingMode = BlendingMode.Additive,
+                                            Alpha = 0
+                                        }
+                                    }
+                                },
+                                logoBeatContainer = new Container
                                 {
                                     AutoSizeAxes = Axes.Both,
                                     Children = new Drawable[]
@@ -151,22 +167,6 @@ namespace osu.Game.Screens.Menu
                                                 },
                                             }
                                         },
-                                        rippleContainer = new Container
-                                        {
-                                            Anchor = Anchor.Centre,
-                                            Origin = Anchor.Centre,
-                                            RelativeSizeAxes = Axes.Both,
-                                            Children = new Drawable[]
-                                            {
-                                                ripple = new Sprite
-                                                {
-                                                    Anchor = Anchor.Centre,
-                                                    Origin = Anchor.Centre,
-                                                    BlendingMode = BlendingMode.Additive,
-                                                    Alpha = 0.15f
-                                                }
-                                            }
-                                        },
                                         impactContainer = new CircularContainer
                                         {
                                             Anchor = Anchor.Centre,
@@ -211,22 +211,23 @@ namespace osu.Game.Screens.Menu
             ripple.Texture = textures.Get(@"Menu/logo");
         }
 
-        protected override void LoadComplete()
-        {
-            base.LoadComplete();
-
-            ripple.ScaleTo(ripple.Scale * 1.1f, 500);
-            ripple.FadeOut(500);
-            ripple.Loop(300);
-        }
-
         protected override void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
         {
             base.OnNewBeat(newBeat, beatLength, timeSignature, kiai);
 
-            logoBeatContainer.ScaleTo(0.97f, beat_in_time, EasingTypes.Out);
+            if (newBeat < 0) return;
+
+            logoBeatContainer.ScaleTo(0.98f, beat_in_time, EasingTypes.Out);
             using (logoBeatContainer.BeginDelayedSequence(beat_in_time))
                 logoBeatContainer.ScaleTo(1, beatLength * 2, EasingTypes.OutQuint);
+
+            ripple.ClearTransforms();
+
+            ripple.ScaleTo(Vector2.One);
+            ripple.Alpha = 0.15f;
+
+            ripple.ScaleTo(ripple.Scale * 1.04f, beatLength, EasingTypes.OutQuint);
+            ripple.FadeOut(beatLength);
         }
 
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)

From 2decb2b2ff929f5a782592a1563fb32073b429ef Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 23 May 2017 16:27:01 +0900
Subject: [PATCH 105/179] Add more flashiness during kiai time

---
 osu.Game/Screens/Menu/OsuLogo.cs | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 738204e30e..74fc7a3f87 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -228,6 +228,15 @@ namespace osu.Game.Screens.Menu
 
             ripple.ScaleTo(ripple.Scale * 1.04f, beatLength, EasingTypes.OutQuint);
             ripple.FadeOut(beatLength);
+
+            if (kiai && flashLayer.Alpha < 0.4f)
+            {
+                flashLayer.ClearTransforms();
+
+                flashLayer.FadeTo(0.14f, beat_in_time, EasingTypes.Out);
+                using (flashLayer.BeginDelayedSequence(beat_in_time))
+                    flashLayer.FadeOut(beatLength);
+            }
         }
 
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)

From a4823bca91514e34b39ebd5b62e50fd096ae7980 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 16:47:47 +0900
Subject: [PATCH 106/179] CI fixes.

---
 osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs | 2 +-
 osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs       | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
index 6d390464fe..cc8897840e 100644
--- a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
+++ b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Timing
         /// </summary>
         public double TimeSpan { get; set; }
 
-        private readonly List<DrawableControlPoint> drawableControlPoints = new List<DrawableControlPoint>();
+        private readonly List<DrawableControlPoint> drawableControlPoints;
 
         public ControlPointContainer(IEnumerable<TimingChange> timingChanges)
         {
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 4f87467706..82ceaeed1a 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -31,12 +31,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
         public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE;
 
         /// <summary>
-        /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="ControlPoint.BeatLength"/> of 1000ms.
+        /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="TimingControlPoint.BeatLength"/> of 1000ms.
         /// </summary>
         private const double scroll_time = 6000;
 
         /// <summary>
-        /// Our adjusted <see cref="scroll_time"/> taking into consideration local <see cref="ControlPoint.BeatLength"/> and other speed multipliers.
+        /// Our adjusted <see cref="scroll_time"/> taking into consideration local <see cref="TimingControlPoint.BeatLength"/> and other speed multipliers.
         /// </summary>
         public double ScrollTime;
 

From 0dd52e4e2930509788aa71a1eaaa47b2082a03d1 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 23 May 2017 17:26:28 +0900
Subject: [PATCH 107/179] Various refactoring

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 44 +++++++++++-------------
 1 file changed, 21 insertions(+), 23 deletions(-)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index 61d3ef10a8..ced6d179d6 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK.Graphics;
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Timing;
 using System;
+using osu.Game.Graphics;
 using TrackAmplitudes = osu.Framework.Audio.Track.Track.TrackAmplitudes;
 
 namespace osu.Game.Screens.Menu
@@ -22,25 +23,22 @@ namespace osu.Game.Screens.Menu
 
         private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
 
-        private static readonly ColourInfo gradient_white_to_transparent_black = ColourInfo.GradientHorizontal(new Color4(255, 255, 255, box_max_alpha), Color4.Black.Opacity(0));
-        private static readonly ColourInfo gradient_transparent_black_to_white = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), new Color4(255, 255, 255, box_max_alpha));
-
         private readonly Box leftBox;
         private readonly Box rightBox;
 
         private const float amplitude_dead_zone = 0.25f;
         private const float alpha_multiplier = (1 - amplitude_dead_zone) / 0.55f;
         private const float kiai_multiplier = (1 - amplitude_dead_zone * 0.95f) / 0.8f;
+
         private const int box_max_alpha = 200;
         private const double box_fade_in_time = 65;
-        private const int box_width = 300;
+        private const int box_width = 200;
 
         public MenuSideFlashes()
         {
             RelativeSizeAxes = Axes.Both;
             Anchor = Anchor.Centre;
             Origin = Anchor.Centre;
-            BlendingMode = BlendingMode.Additive;
             Children = new Drawable[]
             {
                 leftBox = new Box
@@ -51,7 +49,6 @@ namespace osu.Game.Screens.Menu
                     Width = box_width,
                     Alpha = 0,
                     BlendingMode = BlendingMode.Additive,
-                    ColourInfo = gradient_white_to_transparent_black,
                 },
                 rightBox = new Box
                 {
@@ -61,40 +58,41 @@ namespace osu.Game.Screens.Menu
                     Width = box_width,
                     Alpha = 0,
                     BlendingMode = BlendingMode.Additive,
-                    ColourInfo = gradient_transparent_black_to_white,
                 }
             };
         }
 
-        private bool kiai;
-        private double beatLength;
+        [BackgroundDependencyLoader]
+        private void load(OsuGameBase game, OsuColour colours)
+        {
+            beatmap.BindTo(game.Beatmap);
+
+            // linear colour looks better in this case, so let's use it for now.
+            Color4 gradientDark = colours.Blue.Opacity(0).ToLinear();
+            Color4 gradientLight = colours.Blue.Opacity(0.3f).ToLinear();
+
+            leftBox.ColourInfo = ColourInfo.GradientHorizontal(gradientLight, gradientDark);
+            rightBox.ColourInfo = ColourInfo.GradientHorizontal(gradientDark, gradientLight);
+        }
 
         protected override void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
         {
             if (newBeat < 0)
                 return;
 
-            this.kiai = kiai;
-            this.beatLength = beatLength;
-
             if (kiai ? newBeat % 2 == 0 : newBeat % (int)timeSignature == 0)
-                flash(leftBox);
+                flash(leftBox, beatLength, kiai);
             if (kiai ? newBeat % 2 == 1 : newBeat % (int)timeSignature == 0)
-                flash(rightBox);
+                flash(rightBox, beatLength, kiai);
         }
 
-        private void flash(Drawable d)
+        private void flash(Drawable d, double beatLength, bool kiai)
         {
             TrackAmplitudes amp = beatmap.Value.Track.CurrentAmplitudes;
+
             d.FadeTo(Math.Max(0, ((d.Equals(leftBox) ? amp.LeftChannel : amp.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time);
             using (d.BeginDelayedSequence(box_fade_in_time))
                 d.FadeOut(beatLength, EasingTypes.In);
         }
-
-        [BackgroundDependencyLoader]
-        private void load(OsuGameBase game)
-        {
-            beatmap.BindTo(game.Beatmap);
-        }
     }
-}
\ No newline at end of file
+}

From 25a48d832f2ec71e52b3df4a894d3026714a31ae Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 17:36:35 +0900
Subject: [PATCH 108/179] Make kiai time hit object pulse on bar line beats.

---
 osu-framework                                     |  2 +-
 .../Objects/Drawables/Pieces/CirclePiece.cs       | 15 +++++++++++++++
 .../Objects/Drawables/Pieces/TaikoPiece.cs        |  8 +++-----
 3 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/osu-framework b/osu-framework
index 773d60eb6b..71177efb0c 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 773d60eb6b811f395e32a22dc66bb4d2e63a6dbc
+Subproject commit 71177efb0c416361e4b346a92dd61ab20bf333d0
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index 6d3a9e79f6..f182eb6993 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Game.Graphics.Backgrounds;
 using OpenTK.Graphics;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Framework.Audio.Track;
 
 namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
 {
@@ -148,5 +150,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
                 Radius = KiaiMode ? 40 : 8
             };
         }
+
+        protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
+        {
+            if (!effectPoint.KiaiMode)
+                return;
+
+            if (beatIndex % (int)timingPoint.TimeSignature != 0)
+                return;
+
+            background.FadeEdgeEffectTo(Color4.White);
+            using (BeginDelayedSequence(200))
+                background.FadeEdgeEffectTo(AccentColour, 500, EasingTypes.OutQuint);
+        }
     }
 }
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
index 83b2e59e44..d54bfe9e17 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
@@ -5,10 +5,11 @@ using osu.Framework.Graphics.Containers;
 using osu.Game.Graphics;
 using OpenTK;
 using OpenTK.Graphics;
+using osu.Game.Graphics.Containers;
 
 namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
 {
-    public class TaikoPiece : Container, IHasAccentColour
+    public class TaikoPiece : BeatSyncedContainer, IHasAccentColour
     {
         private Color4 accentColour;
         /// <summary>
@@ -17,10 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
         public virtual Color4 AccentColour
         {
             get { return accentColour; }
-            set
-            {
-                accentColour = value;
-            }
+            set { accentColour = value; }
         }
 
         private bool kiaiMode;

From eafe215169b6ba92b813e280d2681e76b7d1b723 Mon Sep 17 00:00:00 2001
From: EVAST9919 <megaman9919@gmail.com>
Date: Tue, 23 May 2017 11:53:12 +0300
Subject: [PATCH 109/179] Simplify Hud visibility change

---
 osu.Game/Screens/Play/HUDOverlay.cs | 20 ++------------------
 1 file changed, 2 insertions(+), 18 deletions(-)

diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 1e57c2ba2a..34522d74ef 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -30,7 +30,6 @@ namespace osu.Game.Screens.Play
         public readonly SongProgress Progress;
         public readonly ModDisplay ModDisplay;
 
-        private Bindable<bool> showKeyCounter;
         private Bindable<bool> showHud;
 
         private static bool hasShownNotificationOnce;
@@ -46,6 +45,7 @@ namespace osu.Game.Screens.Play
         protected HUDOverlay()
         {
             RelativeSizeAxes = Axes.Both;
+            AlwaysPresent = true;
 
             Add(content = new Container
             {
@@ -67,24 +67,8 @@ namespace osu.Game.Screens.Play
         [BackgroundDependencyLoader(true)]
         private void load(OsuConfigManager config, NotificationManager notificationManager)
         {
-            showKeyCounter = config.GetBindable<bool>(OsuSetting.KeyOverlay);
-            showKeyCounter.ValueChanged += keyCounterVisibility =>
-            {
-                if (keyCounterVisibility)
-                    KeyCounter.FadeIn(duration);
-                else
-                    KeyCounter.FadeOut(duration);
-            };
-            showKeyCounter.TriggerChange();
-
             showHud = config.GetBindable<bool>(OsuSetting.ShowInterface);
-            showHud.ValueChanged += hudVisibility =>
-            {
-                if (hudVisibility)
-                    content.FadeIn(duration);
-                else
-                    content.FadeOut(duration);
-            };
+            showHud.ValueChanged += hudVisibility => FadeTo(hudVisibility ? 1 : 0, duration);
             showHud.TriggerChange();
 
             if (!showHud && !hasShownNotificationOnce)

From 4b23cc47eae4d81a2a2a6d4072d5498b9de836ed Mon Sep 17 00:00:00 2001
From: EVAST9919 <megaman9919@gmail.com>
Date: Tue, 23 May 2017 11:53:42 +0300
Subject: [PATCH 110/179] Moved KeyCounter visibility logic to it's own class

---
 osu.Game/Screens/Play/KeyCounterCollection.cs | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs
index 4a561e6036..e53a1dcd99 100644
--- a/osu.Game/Screens/Play/KeyCounterCollection.cs
+++ b/osu.Game/Screens/Play/KeyCounterCollection.cs
@@ -6,14 +6,22 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using OpenTK.Graphics;
 using osu.Framework.Input;
+using osu.Framework.Configuration;
+using osu.Framework.Allocation;
+using osu.Game.Configuration;
 
 namespace osu.Game.Screens.Play
 {
     public class KeyCounterCollection : FillFlowContainer<KeyCounter>
     {
+        private const int duration = 100;
+
+        private Bindable<bool> showKeyCounter;
+
         public KeyCounterCollection()
         {
             AlwaysReceiveInput = true;
+            AlwaysPresent = true;
 
             Direction = FillDirection.Horizontal;
             AutoSizeAxes = Axes.Both;
@@ -34,6 +42,14 @@ namespace osu.Game.Screens.Play
                 counter.ResetCount();
         }
 
+        [BackgroundDependencyLoader]
+        private void load(OsuConfigManager config)
+        {
+            showKeyCounter = config.GetBindable<bool>(OsuSetting.KeyOverlay);
+            showKeyCounter.ValueChanged += keyCounterVisibility => FadeTo(keyCounterVisibility ? 1 : 0, duration);
+            showKeyCounter.TriggerChange();
+        }
+
         //further: change default values here and in KeyCounter if needed, instead of passing them in every constructor
         private bool isCounting;
         public bool IsCounting

From ae94e6ea85007142089bbb61efb1c91612009650 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 17:57:34 +0900
Subject: [PATCH 111/179] Redesign hit explosions.

---
 osu.Game.Rulesets.Taiko/UI/HitExplosion.cs   | 35 +++++++++-----------
 osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 20 +++++------
 2 files changed, 25 insertions(+), 30 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
index 2ebdeaa5b0..57cc42643b 100644
--- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
@@ -16,23 +16,25 @@ namespace osu.Game.Rulesets.Taiko.UI
     /// <summary>
     /// A circle explodes from the hit target to indicate a hitobject has been hit.
     /// </summary>
-    internal class HitExplosion : CircularContainer
+    internal class HitExplosion : Container
     {
-        /// <summary>
-        /// The judgement this hit explosion visualises.
-        /// </summary>
         public readonly TaikoJudgement Judgement;
 
         private readonly Box innerFill;
 
-        public HitExplosion(TaikoJudgement judgement)
+        private bool isRim;
+
+        public HitExplosion(TaikoJudgement judgement, bool isRim)
         {
+            this.isRim = isRim;
+
             Judgement = judgement;
 
-            Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
+            RelativeSizeAxes = Axes.Y;
+            Width = TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_CIRCLE_DIAMETER;
 
-            Anchor = Anchor.Centre;
-            Origin = Anchor.Centre;
+            Anchor = Anchor.CentreLeft;
+            Origin = Anchor.CentreLeft;
 
             RelativePositionAxes = Axes.Both;
 
@@ -54,22 +56,17 @@ namespace osu.Game.Rulesets.Taiko.UI
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-            switch (Judgement.TaikoResult)
-            {
-                case TaikoHitResult.Good:
-                    innerFill.Colour = colours.Green;
-                    break;
-                case TaikoHitResult.Great:
-                    innerFill.Colour = colours.Blue;
-                    break;
-            }
+            if (isRim)
+                innerFill.Colour = colours.BlueDarker;
+            else
+                innerFill.Colour = colours.PinkDarker;
         }
 
         protected override void LoadComplete()
         {
             base.LoadComplete();
 
-            ScaleTo(5f, 1000, EasingTypes.OutQuint);
+            ScaleTo(new Vector2(2f, 1), 1000, EasingTypes.OutQuint);
             FadeOut(500);
 
             Expire();
@@ -80,7 +77,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         /// </summary>
         public void VisualiseSecondHit()
         {
-            ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50);
+            ResizeTo(new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER, 1), 50);
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index e6677ff0cd..d6b2b26b7c 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         /// <summary>
         /// The offset from <see cref="left_area_size"/> which the center of the hit target lies at.
         /// </summary>
-        private const float hit_target_offset = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
+        public const float HIT_TARGET_OFFSET = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
 
         /// <summary>
         /// The size of the left area of the playfield. This area contains the input drum.
@@ -89,21 +89,19 @@ namespace osu.Game.Rulesets.Taiko.UI
                             Margin = new MarginPadding { Left = left_area_size },
                             Children = new Drawable[]
                             {
+                                hitExplosionContainer = new Container<HitExplosion>
+                                {
+                                    RelativeSizeAxes = Axes.Y,
+                                    BlendingMode = BlendingMode.Additive
+                                },
                                 new Container
                                 {
                                     Name = "Masked elements",
                                     RelativeSizeAxes = Axes.Both,
-                                    Padding = new MarginPadding { Left = hit_target_offset },
+                                    Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
                                     Masking = true,
                                     Children = new Drawable[]
                                     {
-                                        hitExplosionContainer = new Container<HitExplosion>
-                                        {
-                                            Anchor = Anchor.CentreLeft,
-                                            Origin = Anchor.Centre,
-                                            RelativeSizeAxes = Axes.Y,
-                                            BlendingMode = BlendingMode.Additive
-                                        },
                                         barLineContainer = new Container<DrawableBarLine>
                                         {
                                             RelativeSizeAxes = Axes.Both,
@@ -123,7 +121,7 @@ namespace osu.Game.Rulesets.Taiko.UI
                                 {
                                     Name = "Judgements",
                                     RelativeSizeAxes = Axes.Y,
-                                    Margin = new MarginPadding { Left = hit_target_offset },
+                                    Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
                                     BlendingMode = BlendingMode.Additive
                                 },
                             }
@@ -217,7 +215,7 @@ namespace osu.Game.Rulesets.Taiko.UI
                     topLevelHitContainer.Add(judgedObject.CreateProxy());
                 }
 
-                hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement));
+                hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement, judgedObject is DrawableRimHit));
             }
             else
                 hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();

From 92552970757214337f02acb659c457676cf5d6ed Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 17:58:12 +0900
Subject: [PATCH 112/179] Cleanup.

---
 .../Objects/Drawables/Pieces/TaikoPiece.cs                 | 1 -
 osu.Game.Rulesets.Taiko/UI/HitExplosion.cs                 | 7 ++-----
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
index d54bfe9e17..5e7e9e6350 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
@@ -1,7 +1,6 @@
 // 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.Containers;
 using osu.Game.Graphics;
 using OpenTK;
 using OpenTK.Graphics;
diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
index 57cc42643b..868f1cd9c0 100644
--- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.UI
 
         private readonly Box innerFill;
 
-        private bool isRim;
+        private readonly bool isRim;
 
         public HitExplosion(TaikoJudgement judgement, bool isRim)
         {
@@ -56,10 +56,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-            if (isRim)
-                innerFill.Colour = colours.BlueDarker;
-            else
-                innerFill.Colour = colours.PinkDarker;
+            innerFill.Colour = isRim ? colours.BlueDarker : colours.PinkDarker;
         }
 
         protected override void LoadComplete()

From 76fe4af9a6c68650ae3ebdbdeb768b08d424638e Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Tue, 23 May 2017 18:06:31 +0900
Subject: [PATCH 113/179] Fix visual test failing.

---
 osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
index ec50f19f98..9dcba02849 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs
@@ -41,7 +41,7 @@ namespace osu.Desktop.VisualTests.Tests
                 Clear();
 
                 ManiaPlayfield playField;
-                Add(playField = new ManiaPlayfield(cols, new List<TimingChange>())
+                Add(playField = new ManiaPlayfield(cols, new List<TimingChange> { new TimingChange { BeatLength = 200 } })
                 {
                     Anchor = Anchor.Centre,
                     Origin = Anchor.Centre,

From c7a241246ecb912897aded1faf75203589f66614 Mon Sep 17 00:00:00 2001
From: EVAST9919 <megaman9919@gmail.com>
Date: Tue, 23 May 2017 12:09:32 +0300
Subject: [PATCH 114/179] Better letterbox settings transition

---
 .../Sections/Graphics/LayoutSettings.cs       | 49 +++++++++----------
 1 file changed, 24 insertions(+), 25 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 9a5fd769cd..8b093c8d79 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -4,6 +4,7 @@
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
 
 namespace osu.Game.Overlays.Settings.Sections.Graphics
 {
@@ -11,11 +12,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
     {
         protected override string Header => "Layout";
 
-        private SettingsSlider<double> letterboxPositionX;
-        private SettingsSlider<double> letterboxPositionY;
+        private FillFlowContainer letterboxSettings;
 
         private Bindable<bool> letterboxing;
 
+        private const int letterbox_container_height = 75;
+        private const int duration = 200;
+
         [BackgroundDependencyLoader]
         private void load(FrameworkConfigManager config)
         {
@@ -33,34 +36,30 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     LabelText = "Letterboxing",
                     Bindable = letterboxing,
                 },
-                letterboxPositionX = new SettingsSlider<double>
+                letterboxSettings = new FillFlowContainer
                 {
-                    LabelText = "Horizontal position",
-                    Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionX)
-                },
-                letterboxPositionY = new SettingsSlider<double>
-                {
-                    LabelText = "Vertical position",
-                    Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionY)
+                    Direction = FillDirection.Vertical,
+                    RelativeSizeAxes = Axes.X,
+                    Masking = true,
+
+                    Children = new Drawable[]
+                    {
+                        new SettingsSlider<double>
+                        {
+                            LabelText = "Horizontal position",
+                            Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionX)
+                        },
+                        new SettingsSlider<double>
+                        {
+                            LabelText = "Vertical position",
+                            Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionY)
+                        },
+                    }
                 },
             };
 
-            letterboxing.ValueChanged += visibilityChanged;
+            letterboxing.ValueChanged += isVisible => letterboxSettings.ResizeHeightTo(isVisible ? letterbox_container_height : 0, duration, EasingTypes.OutQuint);
             letterboxing.TriggerChange();
         }
-
-        private void visibilityChanged(bool newVisibility)
-        {
-            if (newVisibility)
-            {
-                letterboxPositionX.Show();
-                letterboxPositionY.Show();
-            }
-            else
-            {
-                letterboxPositionX.Hide();
-                letterboxPositionY.Hide();
-            }
-        }
     }
 }

From 2e28b10c3678518e496801d799b9b5f6239ad56f Mon Sep 17 00:00:00 2001
From: EVAST9919 <megaman9919@gmail.com>
Date: Tue, 23 May 2017 12:24:16 +0300
Subject: [PATCH 115/179] CI fixes and removed useless property

---
 osu.Game/Screens/Play/HUDOverlay.cs           | 3 +--
 osu.Game/Screens/Play/KeyCounterCollection.cs | 1 -
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 34522d74ef..115611c244 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -45,7 +45,6 @@ namespace osu.Game.Screens.Play
         protected HUDOverlay()
         {
             RelativeSizeAxes = Axes.Both;
-            AlwaysPresent = true;
 
             Add(content = new Container
             {
@@ -68,7 +67,7 @@ namespace osu.Game.Screens.Play
         private void load(OsuConfigManager config, NotificationManager notificationManager)
         {
             showHud = config.GetBindable<bool>(OsuSetting.ShowInterface);
-            showHud.ValueChanged += hudVisibility => FadeTo(hudVisibility ? 1 : 0, duration);
+            showHud.ValueChanged += hudVisibility => content.FadeTo(hudVisibility ? 1 : 0, duration);
             showHud.TriggerChange();
 
             if (!showHud && !hasShownNotificationOnce)
diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs
index e53a1dcd99..6e1c8a34e5 100644
--- a/osu.Game/Screens/Play/KeyCounterCollection.cs
+++ b/osu.Game/Screens/Play/KeyCounterCollection.cs
@@ -21,7 +21,6 @@ namespace osu.Game.Screens.Play
         public KeyCounterCollection()
         {
             AlwaysReceiveInput = true;
-            AlwaysPresent = true;
 
             Direction = FillDirection.Horizontal;
             AutoSizeAxes = Axes.Both;

From 165d6ef1bb19e6c5079dbd95e2afced50f437adc Mon Sep 17 00:00:00 2001
From: EVAST9919 <megaman9919@gmail.com>
Date: Tue, 23 May 2017 12:30:39 +0300
Subject: [PATCH 116/179] Make transition a bit smoother

---
 osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 8b093c8d79..8ff145823c 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
         private Bindable<bool> letterboxing;
 
         private const int letterbox_container_height = 75;
-        private const int duration = 200;
+        private const int duration = 300;
 
         [BackgroundDependencyLoader]
         private void load(FrameworkConfigManager config)

From 564abc1a5bc171a7f62e5f0568fa17ec87efeffa Mon Sep 17 00:00:00 2001
From: EVAST9919 <megaman9919@gmail.com>
Date: Tue, 23 May 2017 14:32:45 +0300
Subject: [PATCH 117/179] using autosize instead of constant height

---
 .../Settings/Sections/Graphics/LayoutSettings.cs  | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 8ff145823c..ea9ccd3492 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -16,8 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
 
         private Bindable<bool> letterboxing;
 
-        private const int letterbox_container_height = 75;
-        private const int duration = 300;
+        private const int transition_duration = 400;
 
         [BackgroundDependencyLoader]
         private void load(FrameworkConfigManager config)
@@ -40,6 +39,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                 {
                     Direction = FillDirection.Vertical,
                     RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    AutoSizeDuration = transition_duration,
+                    AutoSizeEasing = EasingTypes.OutQuint,
                     Masking = true,
 
                     Children = new Drawable[]
@@ -58,7 +60,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                 },
             };
 
-            letterboxing.ValueChanged += isVisible => letterboxSettings.ResizeHeightTo(isVisible ? letterbox_container_height : 0, duration, EasingTypes.OutQuint);
+            letterboxing.ValueChanged += isVisible =>
+            {
+                letterboxSettings.ClearTransforms();
+                letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None;
+
+                if(!isVisible)
+                    letterboxSettings.ResizeHeightTo(0, transition_duration, EasingTypes.OutQuint);
+            };
             letterboxing.TriggerChange();
         }
     }

From 7628cdf52244ab3113fa51b635387871a0d86d82 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 01:38:04 +0900
Subject: [PATCH 118/179] Return first control point in the list if the time is
 before it.

---
 osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 5740c961b1..6809c417a4 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -82,7 +82,7 @@ namespace osu.Game.Beatmaps.ControlPoints
                 return new T();
 
             if (time < list[0].Time)
-                return new T();
+                return list[0];
 
             int index = list.BinarySearch(new T() { Time = time });
 

From 462bbd02bab617953b484a6a74e06bb9a71be87e Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 01:38:28 +0900
Subject: [PATCH 119/179] Simplify expression.

---
 osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 2 --
 1 file changed, 2 deletions(-)

diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 6809c417a4..9dba9cfa19 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -92,8 +92,6 @@ namespace osu.Game.Beatmaps.ControlPoints
 
             index = ~index;
 
-            if (index == list.Count)
-                return list[list.Count - 1];
             return list[index - 1];
         }
     }

From f57b234cc3694ad0ff9d334624280ccfbdcf6ec5 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 01:44:47 +0900
Subject: [PATCH 120/179] Expose Beatmap in BeatSyncedContainer

---
 .../Graphics/Containers/BeatSyncedContainer.cs     | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 5624ca07ef..c0defceac0 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Graphics.Containers
 {
     public class BeatSyncedContainer : Container
     {
-        private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
+        protected readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
 
         private int lastBeat;
         private TimingControlPoint lastTimingPoint;
@@ -25,13 +25,13 @@ namespace osu.Game.Graphics.Containers
 
         protected override void Update()
         {
-            if (beatmap.Value?.Track == null)
+            if (Beatmap.Value?.Track == null)
                 return;
 
-            double currentTrackTime = beatmap.Value.Track.CurrentTime + EarlyActivationMilliseconds;
+            double currentTrackTime = Beatmap.Value.Track.CurrentTime + EarlyActivationMilliseconds;
 
-            TimingControlPoint timingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
-            EffectControlPoint effectPoint = beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
+            TimingControlPoint timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
+            EffectControlPoint effectPoint = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
 
             if (timingPoint.BeatLength == 0)
                 return;
@@ -48,7 +48,7 @@ namespace osu.Game.Graphics.Containers
             double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
 
             using (BeginDelayedSequence(offsetFromBeat, true))
-                OnNewBeat(beatIndex, timingPoint, effectPoint, beatmap.Value.Track.CurrentAmplitudes);
+                OnNewBeat(beatIndex, timingPoint, effectPoint, Beatmap.Value.Track.CurrentAmplitudes);
 
             lastBeat = beatIndex;
             lastTimingPoint = timingPoint;
@@ -57,7 +57,7 @@ namespace osu.Game.Graphics.Containers
         [BackgroundDependencyLoader]
         private void load(OsuGameBase game)
         {
-            beatmap.BindTo(game.Beatmap);
+            Beatmap.BindTo(game.Beatmap);
         }
 
         protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)

From 7e827c4f11bdf925113bf46fd777deb148798bc1 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 01:45:01 +0900
Subject: [PATCH 121/179] Add amplitude adjust

---
 osu.Game/Screens/Menu/OsuLogo.cs | 150 ++++++++++++++++++-------------
 1 file changed, 86 insertions(+), 64 deletions(-)

diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 8826f56180..4259165005 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -31,6 +31,7 @@ namespace osu.Game.Screens.Menu
         private readonly CircularContainer logoContainer;
         private readonly Container logoBounceContainer;
         private readonly Container logoBeatContainer;
+        private readonly Container logoAmplitudeContainer;
         private readonly Container logoHoverContainer;
 
         private SampleChannel sampleClick;
@@ -112,88 +113,95 @@ namespace osu.Game.Screens.Menu
                                         }
                                     }
                                 },
-                                logoBeatContainer = new Container
+                                logoAmplitudeContainer = new Container
                                 {
                                     AutoSizeAxes = Axes.Both,
                                     Children = new Drawable[]
                                     {
-                                        new BufferedContainer
+                                        logoBeatContainer = new Container
                                         {
                                             AutoSizeAxes = Axes.Both,
                                             Children = new Drawable[]
                                             {
-                                                logoContainer = new CircularContainer
+                                                new BufferedContainer
+                                                {
+                                                    AutoSizeAxes = Axes.Both,
+                                                    Children = new Drawable[]
+                                                    {
+                                                        logoContainer = new CircularContainer
+                                                        {
+                                                            Anchor = Anchor.Centre,
+                                                            Origin = Anchor.Centre,
+                                                            RelativeSizeAxes = Axes.Both,
+                                                            Scale = new Vector2(0.88f),
+                                                            Masking = true,
+                                                            Children = new Drawable[]
+                                                            {
+                                                                colourAndTriangles = new Container
+                                                                {
+                                                                    RelativeSizeAxes = Axes.Both,
+                                                                    Anchor = Anchor.Centre,
+                                                                    Origin = Anchor.Centre,
+                                                                    Children = new Drawable[]
+                                                                    {
+                                                                        new Box
+                                                                        {
+                                                                            RelativeSizeAxes = Axes.Both,
+                                                                            Colour = OsuPink,
+                                                                        },
+                                                                        new Triangles
+                                                                        {
+                                                                            TriangleScale = 4,
+                                                                            ColourLight = OsuColour.FromHex(@"ff7db7"),
+                                                                            ColourDark = OsuColour.FromHex(@"de5b95"),
+                                                                            RelativeSizeAxes = Axes.Both,
+                                                                        },
+                                                                    }
+                                                                },
+                                                                flashLayer = new Box
+                                                                {
+                                                                    RelativeSizeAxes = Axes.Both,
+                                                                    BlendingMode = BlendingMode.Additive,
+                                                                    Colour = Color4.White,
+                                                                    Alpha = 0,
+                                                                },
+                                                            },
+                                                        },
+                                                        logo = new Sprite
+                                                        {
+                                                            Anchor = Anchor.Centre,
+                                                            Origin = Anchor.Centre,
+                                                        },
+                                                    }
+                                                },
+                                                impactContainer = new CircularContainer
                                                 {
                                                     Anchor = Anchor.Centre,
                                                     Origin = Anchor.Centre,
+                                                    Alpha = 0,
+                                                    BorderColour = Color4.White,
                                                     RelativeSizeAxes = Axes.Both,
-                                                    Scale = new Vector2(0.88f),
+                                                    BorderThickness = 10,
                                                     Masking = true,
                                                     Children = new Drawable[]
                                                     {
-                                                        colourAndTriangles = new Container
+                                                        new Box
                                                         {
                                                             RelativeSizeAxes = Axes.Both,
-                                                            Anchor = Anchor.Centre,
-                                                            Origin = Anchor.Centre,
-                                                            Children = new Drawable[]
-                                                            {
-                                                                new Box
-                                                                {
-                                                                    RelativeSizeAxes = Axes.Both,
-                                                                    Colour = OsuPink,
-                                                                },
-                                                                new Triangles
-                                                                {
-                                                                    TriangleScale = 4,
-                                                                    ColourLight = OsuColour.FromHex(@"ff7db7"),
-                                                                    ColourDark = OsuColour.FromHex(@"de5b95"),
-                                                                    RelativeSizeAxes = Axes.Both,
-                                                                },
-                                                            }
-                                                        },
-                                                        flashLayer = new Box
-                                                        {
-                                                            RelativeSizeAxes = Axes.Both,
-                                                            BlendingMode = BlendingMode.Additive,
-                                                            Colour = Color4.White,
+                                                            AlwaysPresent = true,
                                                             Alpha = 0,
-                                                        },
-                                                    },
+                                                        }
+                                                    }
                                                 },
-                                                logo = new Sprite
+                                                new MenuVisualisation
                                                 {
                                                     Anchor = Anchor.Centre,
                                                     Origin = Anchor.Centre,
-                                                },
-                                            }
-                                        },
-                                        impactContainer = new CircularContainer
-                                        {
-                                            Anchor = Anchor.Centre,
-                                            Origin = Anchor.Centre,
-                                            Alpha = 0,
-                                            BorderColour = Color4.White,
-                                            RelativeSizeAxes = Axes.Both,
-                                            BorderThickness = 10,
-                                            Masking = true,
-                                            Children = new Drawable[]
-                                            {
-                                                new Box
-                                                {
                                                     RelativeSizeAxes = Axes.Both,
-                                                    AlwaysPresent = true,
-                                                    Alpha = 0,
+                                                    BlendingMode = BlendingMode.Additive,
+                                                    Alpha = 0.2f,
                                                 }
                                             }
-                                        },
-                                        new MenuVisualisation
-                                        {
-                                            Anchor = Anchor.Centre,
-                                            Origin = Anchor.Centre,
-                                            RelativeSizeAxes = Axes.Both,
-                                            BlendingMode = BlendingMode.Additive,
-                                            Alpha = 0.2f,
                                         }
                                     }
                                 }
@@ -205,43 +213,57 @@ namespace osu.Game.Screens.Menu
         }
 
         [BackgroundDependencyLoader]
-        private void load(TextureStore textures, AudioManager audio)
+        private void load(TextureStore textures, AudioManager audio, OsuGameBase game)
         {
             sampleClick = audio.Sample.Get(@"Menu/menuhit");
             logo.Texture = textures.Get(@"Menu/logo");
             ripple.Texture = textures.Get(@"Menu/logo");
         }
 
+        private int lastBeatIndex;
+
         protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
         {
             base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
 
+            lastBeatIndex = beatIndex;
+
             var beatLength = timingPoint.BeatLength;
 
+            float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum);
+
             if (beatIndex < 0) return;
 
-            logoBeatContainer.ScaleTo(0.98f, beat_in_time, EasingTypes.Out);
+            logoBeatContainer.ScaleTo(1 - 0.02f * amplitudeAdjust, beat_in_time, EasingTypes.Out);
             using (logoBeatContainer.BeginDelayedSequence(beat_in_time))
                 logoBeatContainer.ScaleTo(1, beatLength * 2, EasingTypes.OutQuint);
 
             ripple.ClearTransforms();
 
-            ripple.ScaleTo(Vector2.One);
-            ripple.Alpha = 0.15f;
+            ripple.ScaleTo(logoAmplitudeContainer.Scale);
+            ripple.Alpha = 0.15f * amplitudeAdjust;
 
-            ripple.ScaleTo(ripple.Scale * 1.04f, beatLength, EasingTypes.OutQuint);
-            ripple.FadeOut(beatLength);
+            ripple.ScaleTo(logoAmplitudeContainer.Scale * (1 + 0.04f * amplitudeAdjust), beatLength, EasingTypes.OutQuint);
+            ripple.FadeOut(beatLength, EasingTypes.OutQuint);
 
             if (effectPoint.KiaiMode && flashLayer.Alpha < 0.4f)
             {
                 flashLayer.ClearTransforms();
 
-                flashLayer.FadeTo(0.14f, beat_in_time, EasingTypes.Out);
+                flashLayer.FadeTo(0.2f * amplitudeAdjust, beat_in_time, EasingTypes.Out);
                 using (flashLayer.BeginDelayedSequence(beat_in_time))
                     flashLayer.FadeOut(beatLength);
             }
         }
 
+        protected override void Update()
+        {
+            base.Update();
+
+            var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0;
+            logoAmplitudeContainer.ScaleTo(1 - maxAmplitude * 0.04f, 50, EasingTypes.OutQuint);
+        }
+
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
         {
             if (!Interactive) return false;

From 84499275ade05987a2b26ebb89b67ebc11c17775 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 01:58:07 +0900
Subject: [PATCH 122/179] Update framework

---
 osu-framework | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index 79b335e9d4..777996fb97 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 79b335e9d4d6f8ff89524f06dbe324db3cf29513
+Subproject commit 777996fb9731ba1895a5ab1323cbbc97259ff741

From 813b09189c8f77fe272a63d32408b269e62b7e8e Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 02:09:31 +0900
Subject: [PATCH 123/179] Remove unused parameter

---
 osu.Game/Screens/Menu/OsuLogo.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 4259165005..f99e7e80c8 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -213,7 +213,7 @@ namespace osu.Game.Screens.Menu
         }
 
         [BackgroundDependencyLoader]
-        private void load(TextureStore textures, AudioManager audio, OsuGameBase game)
+        private void load(TextureStore textures, AudioManager audio)
         {
             sampleClick = audio.Sample.Get(@"Menu/menuhit");
             logo.Texture = textures.Get(@"Menu/logo");

From 9e7f384203862921fc6ea17d807d5137eb667e22 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 02:18:25 +0900
Subject: [PATCH 124/179] Fix returning incorrect control points for non-timing
 points.

---
 .../Beatmaps/ControlPoints/ControlPointInfo.cs     | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 9dba9cfa19..38b175eed9 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -1,6 +1,7 @@
 // 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 System.Collections.Generic;
 using System.Linq;
 using osu.Framework.Lists;
@@ -55,7 +56,7 @@ namespace osu.Game.Beatmaps.ControlPoints
         /// </summary>
         /// <param name="time">The time to find the timing control point at.</param>
         /// <returns>The timing control point.</returns>
-        public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time);
+        public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, () => TimingPoints[0]);
 
         /// <summary>
         /// Finds the maximum BPM represented by any timing control point.
@@ -75,14 +76,21 @@ namespace osu.Game.Beatmaps.ControlPoints
         public double BPMMode =>
             60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
 
-        private T binarySearch<T>(SortedList<T> list, double time)
+        /// <summary>
+        /// Binary searches one of the control point lists to find the active contro point at <paramref name="time"/>.
+        /// </summary>
+        /// <param name="list">The list to search.</param>
+        /// <param name="time">The time to find the control point at.</param>
+        /// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points.</param>
+        /// <returns></returns>
+        private T binarySearch<T>(SortedList<T> list, double time, Func<T> prePoint = null)
             where T : ControlPoint, new()
         {
             if (list.Count == 0)
                 return new T();
 
             if (time < list[0].Time)
-                return list[0];
+                return prePoint?.Invoke() ?? new T();
 
             int index = list.BinarySearch(new T() { Time = time });
 

From 2d1df8fd8a7c1eaaf58de8d4e42429222d0a90e4 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 02:22:28 +0900
Subject: [PATCH 125/179] xmldoc fixes.

---
 osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 38b175eed9..c8e5eba5dd 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -77,12 +77,12 @@ namespace osu.Game.Beatmaps.ControlPoints
             60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
 
         /// <summary>
-        /// Binary searches one of the control point lists to find the active contro point at <paramref name="time"/>.
+        /// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>.
         /// </summary>
         /// <param name="list">The list to search.</param>
         /// <param name="time">The time to find the control point at.</param>
         /// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points.</param>
-        /// <returns></returns>
+        /// <returns>The active control point at <paramref name="time"/>.</returns>
         private T binarySearch<T>(SortedList<T> list, double time, Func<T> prePoint = null)
             where T : ControlPoint, new()
         {

From 41824e01796b5f4fbb4986f48a43446500c953d8 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 02:24:10 +0900
Subject: [PATCH 126/179] Add comment.

---
 osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index c8e5eba5dd..d4f1c8df4b 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -100,6 +100,8 @@ namespace osu.Game.Beatmaps.ControlPoints
 
             index = ~index;
 
+            // BinarySearch will return the index of the first element _greater_ than the search
+            // This is the inactive point - the active point is the one before it (index - 1)
             return list[index - 1];
         }
     }

From 4f17a4fe91fdf5f9e78acae54b555fc3c07617e7 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 23 May 2017 14:34:34 -0300
Subject: [PATCH 127/179] Make result counts scroll with the panels

---
 .../Tests/TestCaseDirect.cs                   |  4 +-
 osu.Game/Overlays/Direct/FilterControl.cs     | 58 ---------------
 osu.Game/Overlays/DirectOverlay.cs            | 72 +++++++++++++++++--
 3 files changed, 69 insertions(+), 65 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
index c68796a9ab..c1f2307f20 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -23,9 +23,9 @@ namespace osu.Desktop.VisualTests.Tests
 
             Add(direct = new DirectOverlay());
             newBeatmaps();
-            direct.ResultCounts = new ResultCounts(1, 432, 3);
 
-            AddStep(@"Toggle", direct.ToggleVisibility);
+            AddStep(@"toggle", direct.ToggleVisibility);
+            AddStep(@"result counts", () => direct.ResultCounts = new ResultCounts(1, 4, 13));
         }
 
         [BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 185ab7c321..7d8a4a6a85 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Game.Database;
 using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
 using osu.Game.Graphics.UserInterface;
 
 using Container = osu.Framework.Graphics.Containers.Container;
@@ -23,34 +22,16 @@ namespace osu.Game.Overlays.Direct
     {
         public static readonly float HEIGHT = 35 + 32 + 30 + padding * 2; // search + mode toggle buttons + sort tabs + padding
 
-        /// <summary>
-        /// The height of the content below the filter control (tab strip + result count text).
-        /// </summary>
-        public static readonly float LOWER_HEIGHT = 21;
-
         private const float padding = 10;
 
         private readonly Box tabStrip;
         private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
-        private readonly FillFlowContainer resultCountsContainer;
-        private readonly OsuSpriteText resultCountsText;
 
         public readonly SearchTextBox Search;
         public readonly SortTabControl SortTabs;
         public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
         public readonly Bindable<PanelDisplayStyle> DisplayStyle = new Bindable<PanelDisplayStyle>();
 
-        private ResultCounts resultCounts;
-        public ResultCounts ResultCounts
-        {
-            get { return resultCounts; }
-            set
-            {
-                resultCounts = value;
-                updateResultCounts();
-            }
-        }
-
         protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || RankStatusDropdown.Contains(screenSpacePos);
 
         public FilterControl()
@@ -127,41 +108,17 @@ namespace osu.Game.Overlays.Direct
                         },
                     },
                 },
-                resultCountsContainer = new FillFlowContainer
-                {
-                    Anchor = Anchor.BottomLeft,
-                    Origin = Anchor.TopLeft,
-                    AutoSizeAxes = Axes.Both,
-                    Direction = FillDirection.Horizontal,
-                    Margin = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Top = 6 },
-                    Children = new Drawable[]
-                    {
-                        new OsuSpriteText
-                        {
-                            Text = @"Found ",
-                            TextSize = 15,
-                        },
-                        resultCountsText = new OsuSpriteText
-                        {
-                            TextSize = 15,
-                            Font = @"Exo2.0-Bold",
-                        },
-                    }
-                },
             };
 
             RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
             SortTabs.Current.Value = SortCriteria.Title;
             SortTabs.Current.TriggerChange();
-
-            updateResultCounts();
         }
 
         [BackgroundDependencyLoader(true)]
         private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
         {
             tabStrip.Colour = colours.Yellow;
-            resultCountsContainer.Colour = colours.Yellow;
             RankStatusDropdown.AccentColour = colours.BlueDark;
 
             var b = new Bindable<RulesetInfo>(); //backup bindable incase the game is null
@@ -171,21 +128,6 @@ namespace osu.Game.Overlays.Direct
             }
         }
 
-        private void updateResultCounts()
-        {
-            resultCountsContainer.FadeTo(ResultCounts == null ? 0 : 1, 200, EasingTypes.Out);
-            if (resultCounts == null) return;
-
-            resultCountsText.Text = pluralize(@"Artist", ResultCounts?.Artists ?? 0) + ", " +
-                                    pluralize(@"Song", ResultCounts?.Songs ?? 0) + ", " +
-                                    pluralize(@"Tag", ResultCounts?.Tags ?? 0);
-        }
-
-        private string pluralize(string prefix, int value)
-        {
-            return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
-        }
-
         private class DirectSearchTextBox : SearchTextBox
         {
             protected override Color4 BackgroundUnfocused => backgroundColour;
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 928ab3b300..368518fc6d 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -4,6 +4,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using OpenTK;
+using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
@@ -11,6 +12,7 @@ using osu.Framework.Input;
 using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Sprites;
 using osu.Game.Overlays.Direct;
 
 namespace osu.Game.Overlays
@@ -21,6 +23,8 @@ namespace osu.Game.Overlays
         private const float panel_padding = 10f;
 
         private readonly FilterControl filter;
+        private readonly FillFlowContainer resultCountsContainer;
+        private readonly OsuSpriteText resultCountsText;
         private readonly FillFlowContainer<DirectPanel> panels;
 
         private IEnumerable<BeatmapSetInfo> beatmapSets;
@@ -36,10 +40,17 @@ namespace osu.Game.Overlays
             }
         }
 
+        private ResultCounts resultCounts;
         public ResultCounts ResultCounts
         {
-            get { return filter.ResultCounts; }
-            set { filter.ResultCounts = value; }
+            get { return resultCounts; }
+            set
+            {
+                if (value == ResultCounts) return;
+                resultCounts = value;
+
+                updateResultCounts();
+            }
         }
 
         public DirectOverlay()
@@ -88,12 +99,40 @@ namespace osu.Game.Overlays
                             ScrollDraggerVisible = false,
                             Children = new Drawable[]
                             {
-                                panels = new FillFlowContainer<DirectPanel>
+                                new FillFlowContainer
                                 {
                                     RelativeSizeAxes = Axes.X,
                                     AutoSizeAxes = Axes.Y,
-                                    Padding = new MarginPadding { Top = FilterControl.LOWER_HEIGHT + panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
-                                    Spacing = new Vector2(panel_padding),
+                                    Direction = FillDirection.Vertical,
+                                    Children = new Drawable[]
+                                    {
+                                        resultCountsContainer = new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.Both,
+                                            Direction = FillDirection.Horizontal,
+                                            Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
+                                            Children = new Drawable[]
+                                            {
+                                                new OsuSpriteText
+                                                {
+                                                    Text = @"Found ",
+                                                    TextSize = 15,
+                                                },
+                                                resultCountsText = new OsuSpriteText
+                                                {
+                                                    TextSize = 15,
+                                                    Font = @"Exo2.0-Bold",
+                                                },
+                                            }
+                                        },
+                                        panels = new FillFlowContainer<DirectPanel>
+                                        {
+                                            RelativeSizeAxes = Axes.X,
+                                            AutoSizeAxes = Axes.Y,
+                                            Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
+                                            Spacing = new Vector2(panel_padding),
+                                        },
+                                    },
                                 },
                             },
                         },
@@ -115,6 +154,29 @@ namespace osu.Game.Overlays
             filter.Search.Exit = Hide;
             filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
             filter.DisplayStyle.ValueChanged += recreatePanels;
+
+            updateResultCounts();
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            resultCountsContainer.Colour = colours.Yellow;
+        }
+
+        private void updateResultCounts()
+        {
+            resultCountsContainer.FadeTo(ResultCounts == null ? 0f : 1f, 200, EasingTypes.Out);
+            if (ResultCounts == null) return;
+
+            resultCountsText.Text = pluralize(@"Artist", ResultCounts?.Artists ?? 0) + ", " +
+            						pluralize(@"Song", ResultCounts?.Songs ?? 0) + ", " +
+            						pluralize(@"Tag", ResultCounts?.Tags ?? 0);
+        }
+
+        private string pluralize(string prefix, int value)
+        {
+        	return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
         }
 
         private void recreatePanels(PanelDisplayStyle displayStyle)

From 7b9eacc213d181bb57a9feb9add60a60021d243e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 23 May 2017 14:41:30 -0300
Subject: [PATCH 128/179] CI fixes

---
 osu.Game/Overlays/DirectOverlay.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 368518fc6d..0572598380 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -170,13 +170,13 @@ namespace osu.Game.Overlays
             if (ResultCounts == null) return;
 
             resultCountsText.Text = pluralize(@"Artist", ResultCounts?.Artists ?? 0) + ", " +
-            						pluralize(@"Song", ResultCounts?.Songs ?? 0) + ", " +
-            						pluralize(@"Tag", ResultCounts?.Tags ?? 0);
+                pluralize(@"Song", ResultCounts?.Songs ?? 0) + ", " +
+                pluralize(@"Tag", ResultCounts?.Tags ?? 0);
         }
 
         private string pluralize(string prefix, int value)
         {
-        	return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
+    	    return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
         }
 
         private void recreatePanels(PanelDisplayStyle displayStyle)

From aa9a636c3c59e884f475345ff59efcb1d727e6b3 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 23 May 2017 14:42:57 -0300
Subject: [PATCH 129/179] Indentation

---
 osu.Game/Overlays/DirectOverlay.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 0572598380..49a3a3abf9 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -170,8 +170,8 @@ namespace osu.Game.Overlays
             if (ResultCounts == null) return;
 
             resultCountsText.Text = pluralize(@"Artist", ResultCounts?.Artists ?? 0) + ", " +
-                pluralize(@"Song", ResultCounts?.Songs ?? 0) + ", " +
-                pluralize(@"Tag", ResultCounts?.Tags ?? 0);
+                                    pluralize(@"Song", ResultCounts?.Songs ?? 0) + ", " +
+                                    pluralize(@"Tag", ResultCounts?.Tags ?? 0);
         }
 
         private string pluralize(string prefix, int value)

From 7a4b4761218b0d44a7ea36f513763e1e717d3888 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 23 May 2017 14:46:23 -0300
Subject: [PATCH 130/179] Tab character

---
 osu.Game/Overlays/DirectOverlay.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 49a3a3abf9..64b7a1ac28 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -176,7 +176,7 @@ namespace osu.Game.Overlays
 
         private string pluralize(string prefix, int value)
         {
-    	    return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
+            return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
         }
 
         private void recreatePanels(PanelDisplayStyle displayStyle)

From 0a385055dc24aae6253de7971e77106c68f7af25 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 02:53:08 +0900
Subject: [PATCH 131/179] Remove Func<T>'d-ness.

---
 osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index d4f1c8df4b..76ff86d5c4 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Beatmaps.ControlPoints
         /// </summary>
         /// <param name="time">The time to find the timing control point at.</param>
         /// <returns>The timing control point.</returns>
-        public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, () => TimingPoints[0]);
+        public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault());
 
         /// <summary>
         /// Finds the maximum BPM represented by any timing control point.
@@ -81,16 +81,16 @@ namespace osu.Game.Beatmaps.ControlPoints
         /// </summary>
         /// <param name="list">The list to search.</param>
         /// <param name="time">The time to find the control point at.</param>
-        /// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points.</param>
+        /// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param>
         /// <returns>The active control point at <paramref name="time"/>.</returns>
-        private T binarySearch<T>(SortedList<T> list, double time, Func<T> prePoint = null)
+        private T binarySearch<T>(SortedList<T> list, double time, T prePoint = null)
             where T : ControlPoint, new()
         {
             if (list.Count == 0)
                 return new T();
 
             if (time < list[0].Time)
-                return prePoint?.Invoke() ?? new T();
+                return prePoint ?? new T();
 
             int index = list.BinarySearch(new T() { Time = time });
 

From b477e5cd9e21dae9d6b6d97641b2777b47017f56 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 02:53:21 +0900
Subject: [PATCH 132/179] Fix potential nullref

---
 osu.Game/Screens/Menu/OsuLogo.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index f99e7e80c8..116f7291e2 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -260,7 +260,7 @@ namespace osu.Game.Screens.Menu
         {
             base.Update();
 
-            var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0;
+            var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value?.Track?.CurrentAmplitudes.Maximum ?? 0 : 0;
             logoAmplitudeContainer.ScaleTo(1 - maxAmplitude * 0.04f, 50, EasingTypes.OutQuint);
         }
 
@@ -312,4 +312,4 @@ namespace osu.Game.Screens.Menu
             impactContainer.ScaleTo(1.12f, 250);
         }
     }
-}
\ No newline at end of file
+}

From be1ae2bd8eafa6b16494b3b4e16eff8675931b58 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 23 May 2017 15:08:02 -0300
Subject: [PATCH 133/179] Remove ??, value can never be null

---
 osu.Game/Overlays/DirectOverlay.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 64b7a1ac28..8fdc591371 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -169,9 +169,9 @@ namespace osu.Game.Overlays
             resultCountsContainer.FadeTo(ResultCounts == null ? 0f : 1f, 200, EasingTypes.Out);
             if (ResultCounts == null) return;
 
-            resultCountsText.Text = pluralize(@"Artist", ResultCounts?.Artists ?? 0) + ", " +
-                                    pluralize(@"Song", ResultCounts?.Songs ?? 0) + ", " +
-                                    pluralize(@"Tag", ResultCounts?.Tags ?? 0);
+            resultCountsText.Text = pluralize(@"Artist", ResultCounts.Artists) + ", " +
+                                    pluralize(@"Song", ResultCounts.Songs) + ", " +
+                                    pluralize(@"Tag", ResultCounts.Tags);
         }
 
         private string pluralize(string prefix, int value)

From 09adb23591fb4b393dde7ca84d919c0ff3c8aaa7 Mon Sep 17 00:00:00 2001
From: MrTheMake <marc.stephan96@hotmail.de>
Date: Wed, 24 May 2017 02:22:30 +0200
Subject: [PATCH 134/179] Fix scheduled task not being canceled

---
 osu.Game/Screens/Select/SongSelect.cs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index e9ead7c9c0..eb5fb6d258 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -319,7 +319,10 @@ namespace osu.Game.Screens.Select
             bool beatmapSetChange = false;
 
             if (beatmap.Equals(Beatmap?.BeatmapInfo))
+            {
+                selectionChangedDebounce?.Cancel();
                 return;
+            }
 
             if (beatmap.BeatmapSetInfoID == selectionChangeNoBounce?.BeatmapSetInfoID)
                 sampleChangeDifficulty.Play();
@@ -330,7 +333,7 @@ namespace osu.Game.Screens.Select
             }
 
             selectionChangeNoBounce = beatmap;
-
+            
             selectionChangedDebounce?.Cancel();
             selectionChangedDebounce = Scheduler.AddDelayed(delegate
             {

From 0616256bd06f32b6199a6e883afca839505eff57 Mon Sep 17 00:00:00 2001
From: MrTheMake <marc.stephan96@hotmail.de>
Date: Wed, 24 May 2017 02:23:52 +0200
Subject: [PATCH 135/179] CI fix

---
 osu.Game/Screens/Select/SongSelect.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index eb5fb6d258..7c45738d59 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -333,7 +333,7 @@ namespace osu.Game.Screens.Select
             }
 
             selectionChangeNoBounce = beatmap;
-            
+
             selectionChangedDebounce?.Cancel();
             selectionChangedDebounce = Scheduler.AddDelayed(delegate
             {

From 67774192dd5635c9777c103afc5da6b475cc778f Mon Sep 17 00:00:00 2001
From: MrTheMake <marc.stephan96@hotmail.de>
Date: Wed, 24 May 2017 02:30:32 +0200
Subject: [PATCH 136/179] Formatting fixes

---
 osu.Game/Screens/Select/SongSelect.cs | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 7c45738d59..d0c5e1adfd 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -316,13 +316,12 @@ namespace osu.Game.Screens.Select
         /// </summary>
         private void selectionChanged(BeatmapInfo beatmap)
         {
-            bool beatmapSetChange = false;
+            selectionChangedDebounce?.Cancel();
 
             if (beatmap.Equals(Beatmap?.BeatmapInfo))
-            {
-                selectionChangedDebounce?.Cancel();
                 return;
-            }
+
+            bool beatmapSetChange = false;
 
             if (beatmap.BeatmapSetInfoID == selectionChangeNoBounce?.BeatmapSetInfoID)
                 sampleChangeDifficulty.Play();
@@ -334,7 +333,6 @@ namespace osu.Game.Screens.Select
 
             selectionChangeNoBounce = beatmap;
 
-            selectionChangedDebounce?.Cancel();
             selectionChangedDebounce = Scheduler.AddDelayed(delegate
             {
                 Beatmap = database.GetWorkingBeatmap(beatmap, Beatmap);

From 24f64c881556e46ef210c76f38d883aed9a79b51 Mon Sep 17 00:00:00 2001
From: MrTheMake <marc.stephan96@hotmail.de>
Date: Wed, 24 May 2017 02:38:05 +0200
Subject: [PATCH 137/179] More formatting

---
 osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +-
 osu.Game/Screens/Select/SongSelect.cs      | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index 2d6d212130..130642b9c7 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -170,8 +170,8 @@ namespace osu.Game.Screens.Select
             List<BeatmapGroup> visibleGroups = groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden).ToList();
             if (visibleGroups.Count < 1)
                 return;
-            BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
 
+            BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
             BeatmapPanel panel = group.BeatmapPanels[RNG.Next(group.BeatmapPanels.Count)];
 
             selectGroup(group, panel);
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index d0c5e1adfd..41fa53e8a3 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -322,7 +322,6 @@ namespace osu.Game.Screens.Select
                 return;
 
             bool beatmapSetChange = false;
-
             if (beatmap.BeatmapSetInfoID == selectionChangeNoBounce?.BeatmapSetInfoID)
                 sampleChangeDifficulty.Play();
             else

From 207d6e4ac3f2303000bdb48ed7bbc12a1439071e Mon Sep 17 00:00:00 2001
From: ColdVolcano <mineflamecraft@hotmail.com>
Date: Tue, 23 May 2017 20:01:20 -0500
Subject: [PATCH 138/179] Update to new syntax of OnNewBeat

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 26 +++++++++++-------------
 1 file changed, 12 insertions(+), 14 deletions(-)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index ced6d179d6..0cf1fa54fa 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -3,17 +3,17 @@
 
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
 using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Colour;
-using osu.Game.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.Timing;
-using System;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Graphics;
-using TrackAmplitudes = osu.Framework.Audio.Track.Track.TrackAmplitudes;
+using osu.Game.Graphics.Containers;
+using System;
 
 namespace osu.Game.Screens.Menu
 {
@@ -75,22 +75,20 @@ namespace osu.Game.Screens.Menu
             rightBox.ColourInfo = ColourInfo.GradientHorizontal(gradientDark, gradientLight);
         }
 
-        protected override void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
+        protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
         {
-            if (newBeat < 0)
+            if (beatIndex < 0)
                 return;
 
-            if (kiai ? newBeat % 2 == 0 : newBeat % (int)timeSignature == 0)
-                flash(leftBox, beatLength, kiai);
-            if (kiai ? newBeat % 2 == 1 : newBeat % (int)timeSignature == 0)
-                flash(rightBox, beatLength, kiai);
+            if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % (int)timingPoint.TimeSignature == 0)
+                flash(leftBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes);
+            if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % (int)timingPoint.TimeSignature == 0)
+                flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes);
         }
 
-        private void flash(Drawable d, double beatLength, bool kiai)
+        private void flash(Drawable d, double beatLength, bool kiai, TrackAmplitudes amplitudes)
         {
-            TrackAmplitudes amp = beatmap.Value.Track.CurrentAmplitudes;
-
-            d.FadeTo(Math.Max(0, ((d.Equals(leftBox) ? amp.LeftChannel : amp.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time);
+            d.FadeTo(Math.Max(0, ((d.Equals(leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time);
             using (d.BeginDelayedSequence(box_fade_in_time))
                 d.FadeOut(beatLength, EasingTypes.In);
         }

From 53f489b447c1108d67d7f666387ac42c00cc3b86 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 11:50:12 +0900
Subject: [PATCH 139/179] Remove using

---
 osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 76ff86d5c4..acf90931ac 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -1,7 +1,6 @@
 // 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 System.Collections.Generic;
 using System.Linq;
 using osu.Framework.Lists;

From 247d8e9b21edb038de7a7869c349d5797d34f92e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 00:23:48 -0300
Subject: [PATCH 140/179] Replace "Connected as _" in login form with a
 UserPanel

---
 osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs         | 4 ++--
 .../Overlays/Settings/Sections/General/LoginSettings.cs    | 7 +++++--
 osu.Game/Users/UserPanel.cs                                | 1 -
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
index 7c2745ee0f..254c5bc243 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
@@ -33,14 +33,14 @@ namespace osu.Desktop.VisualTests.Tests
                         Id = 3103765,
                         Country = new Country { FlagName = @"JP" },
                         CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3103765/5b012e13611d5761caa7e24fecb3d3a16e1cf48fc2a3032cfd43dd444af83d82.jpeg"
-                    }),
+                    }) { Width = 300 },
                     peppy = new UserPanel(new User
                     {
                         Username = @"peppy",
                         Id = 2,
                         Country = new Country { FlagName = @"AU" },
                         CoverUrl = @"https://assets.ppy.sh/user-profile-covers/2/08cad88747c235a64fca5f1b770e100f120827ded1ffe3b66bfcd19c940afa65.jpeg"
-                    }),
+                    }) { Width = 300 },
                 },
             });
 
diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 86a47d8a95..73521a31c3 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -12,6 +12,7 @@ using osu.Game.Graphics.UserInterface;
 using osu.Game.Online.API;
 using OpenTK;
 using osu.Framework.Input;
+using osu.Game.Users;
 
 namespace osu.Game.Overlays.Settings.Sections.General
 {
@@ -71,11 +72,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
                     };
                     break;
                 case APIState.Online:
+                    UserPanel p;
                     Children = new Drawable[]
                     {
-                        new OsuSpriteText
+                        p = new UserPanel(api.LocalUser.Value)
                         {
-                            Text = $"Connected as {api.Username}!",
+                            RelativeSizeAxes = Axes.X,
                         },
                         new OsuButton
                         {
@@ -84,6 +86,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
                             Action = api.Logout
                         }
                     };
+                    p.Status.Value = new UserStatusOnline();
                     break;
             }
 
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 3502443d49..f32158e00b 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -30,7 +30,6 @@ namespace osu.Game.Users
 
         public UserPanel(User user)
         {
-            Width = 300;
             Height = height;
             Masking = true;
             CornerRadius = 5;

From 2be1b00a766d063305575522f345183eb64dd81f Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 00:45:56 -0300
Subject: [PATCH 141/179] Hide status bar when Status is null

---
 .../Tests/TestCaseUserPanel.cs                  |  1 +
 .../Settings/Sections/General/LoginSettings.cs  |  4 +---
 osu.Game/Users/UserPanel.cs                     | 17 ++++++++++++-----
 3 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
index 254c5bc243..513bf24e0d 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
@@ -51,6 +51,7 @@ namespace osu.Desktop.VisualTests.Tests
             AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
             AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
             AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); });
+            AddStep(@"null status", () => { flyte.Status.Value = null; });
         }
     }
 }
diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 73521a31c3..d94388ed87 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -72,10 +72,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
                     };
                     break;
                 case APIState.Online:
-                    UserPanel p;
                     Children = new Drawable[]
                     {
-                        p = new UserPanel(api.LocalUser.Value)
+                        new UserPanel(api.LocalUser.Value)
                         {
                             RelativeSizeAxes = Axes.X,
                         },
@@ -86,7 +85,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
                             Action = api.Logout
                         }
                     };
-                    p.Status.Value = new UserStatusOnline();
                     break;
             }
 
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index f32158e00b..df37b339c3 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -23,6 +23,7 @@ namespace osu.Game.Users
 
         private OsuColour colours;
 
+        private readonly Container statusBar;
         private readonly Box statusBg;
         private readonly OsuSpriteText statusMessage;
 
@@ -30,7 +31,7 @@ namespace osu.Game.Users
 
         public UserPanel(User user)
         {
-            Height = height;
+            Height = height - status_height;
             Masking = true;
             CornerRadius = 5;
             EdgeEffect = new EdgeEffect
@@ -54,8 +55,9 @@ namespace osu.Game.Users
                 },
                 new Container
                 {
-                    RelativeSizeAxes = Axes.Both,
-                    Padding = new MarginPadding { Top = content_padding, Bottom = status_height + content_padding, Left = content_padding, Right = content_padding },
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Padding = new MarginPadding { Top = content_padding, Left = content_padding, Right = content_padding },
                     Children = new Drawable[]
                     {
                         new UpdateableAvatar
@@ -114,12 +116,12 @@ namespace osu.Game.Users
                         },
                     },
                 },
-                new Container
+                statusBar = new Container
                 {
                     Anchor = Anchor.BottomLeft,
                     Origin = Anchor.BottomLeft,
                     RelativeSizeAxes = Axes.X,
-                    Height = status_height,
+                    Alpha = 0f,
                     Children = new Drawable[]
                     {
                         statusBg = new Box
@@ -174,6 +176,11 @@ namespace osu.Game.Users
 
         private void displayStatus(UserStatus status)
         {
+            statusBar.ResizeHeightTo(status == null ? 0f : status_height, 500, EasingTypes.OutQuint);
+            statusBar.FadeTo(status == null ? 0f : 1f, 500, EasingTypes.OutQuint);
+            ResizeHeightTo(status == null ? height - status_height : height, 500, EasingTypes.OutQuint);
+            if (status == null) return;
+
             statusBg.FadeColour(status.GetAppropriateColour(colours), 500, EasingTypes.OutQuint);
             statusMessage.Text = status.Message;
         }

From a9d1e54c27f6f5778c208d6c79ec7c866e44931e Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 13:05:11 +0900
Subject: [PATCH 142/179] Adjust triangle movement based on amplitude

---
 osu.Game/Graphics/Backgrounds/Triangles.cs |  7 ++++++-
 osu.Game/Screens/Menu/OsuLogo.cs           | 11 ++++++++++-
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 830d0adc97..5cca57be8a 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -44,6 +44,11 @@ namespace osu.Game.Graphics.Backgrounds
         /// </summary>
         public bool HideAlphaDiscrepancies = true;
 
+        /// <summary>
+        /// The relative velocity of the triangles. Default is 1.
+        /// </summary>
+        public float Velocity = 1;
+
         public float TriangleScale
         {
             get { return triangleScale; }
@@ -78,7 +83,7 @@ namespace osu.Game.Graphics.Backgrounds
             foreach (var t in Children)
             {
                 t.Alpha = adjustedAlpha;
-                t.Position -= new Vector2(0, (float)(t.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale);
+                t.Position -= new Vector2(0, (float)(t.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale * Velocity);
                 if (ExpireOffScreenTriangles && t.DrawPosition.Y + t.DrawSize.Y * t.Scale.Y < 0)
                     t.Expire();
             }
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 116f7291e2..3bd27073c6 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Graphics.Textures;
 using osu.Framework.Input;
+using osu.Framework.MathUtils;
 using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
@@ -38,6 +39,8 @@ namespace osu.Game.Screens.Menu
 
         private readonly Container colourAndTriangles;
 
+        private Triangles triangles;
+
         public Action Action;
 
         public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f;
@@ -149,7 +152,7 @@ namespace osu.Game.Screens.Menu
                                                                             RelativeSizeAxes = Axes.Both,
                                                                             Colour = OsuPink,
                                                                         },
-                                                                        new Triangles
+                                                                        triangles = new Triangles
                                                                         {
                                                                             TriangleScale = 4,
                                                                             ColourLight = OsuColour.FromHex(@"ff7db7"),
@@ -260,8 +263,14 @@ namespace osu.Game.Screens.Menu
         {
             base.Update();
 
+            const float velocity_adjust_cutoff = 0.98f;
             var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value?.Track?.CurrentAmplitudes.Maximum ?? 0 : 0;
             logoAmplitudeContainer.ScaleTo(1 - maxAmplitude * 0.04f, 50, EasingTypes.OutQuint);
+
+            if (maxAmplitude > velocity_adjust_cutoff)
+                triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50;
+            else
+                triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, 1, 0.995f, Time.Elapsed);
         }
 
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)

From 16d9a677d0b66359c00a82d0962aa17335972e14 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 13:05:28 +0900
Subject: [PATCH 143/179] Add a low-end cutoff for scale adjust

---
 osu.Game/Screens/Menu/OsuLogo.cs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 3bd27073c6..b1979063dd 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -263,9 +263,11 @@ namespace osu.Game.Screens.Menu
         {
             base.Update();
 
+            const float scale_adjust_cutoff = 0.4f;
             const float velocity_adjust_cutoff = 0.98f;
+
             var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value?.Track?.CurrentAmplitudes.Maximum ?? 0 : 0;
-            logoAmplitudeContainer.ScaleTo(1 - maxAmplitude * 0.04f, 50, EasingTypes.OutQuint);
+            logoAmplitudeContainer.ScaleTo(1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 75, EasingTypes.OutQuint);
 
             if (maxAmplitude > velocity_adjust_cutoff)
                 triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50;

From 03f9a863668e960c235cd955688a19cff3fe0abd Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 13:29:12 +0900
Subject: [PATCH 144/179] Add missing readonly

---
 osu.Game/Screens/Menu/OsuLogo.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index b1979063dd..44b7b6bceb 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu
 
         private readonly Container colourAndTriangles;
 
-        private Triangles triangles;
+        private readonly Triangles triangles;
 
         public Action Action;
 

From 15ee2b802ee94bbb7306acf4d3b8edc31023be6d Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 13:41:07 +0900
Subject: [PATCH 145/179] Hide direct overlay when requested

---
 osu.Game/OsuGame.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 96e4910f1e..886ff4f8d1 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -288,6 +288,7 @@ namespace osu.Game
                 Toolbar.State = Visibility.Hidden;
                 musicController.State = Visibility.Hidden;
                 chat.State = Visibility.Hidden;
+                direct.State = Visibility.Hidden;
             }
             else
             {

From 5e0194077006416718752493291a67f2a07bacaa Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 13:43:10 +0900
Subject: [PATCH 146/179] Compare with private field

---
 osu.Game/Overlays/DirectOverlay.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 8fdc591371..62b57d29e3 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Overlays
             get { return resultCounts; }
             set
             {
-                if (value == ResultCounts) return;
+                if (value == resultCounts) return;
                 resultCounts = value;
 
                 updateResultCounts();

From b08668b6d916de4dcedfc366d650066a2915aff2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 02:19:45 -0300
Subject: [PATCH 147/179] Remove @ from to-be-localized strings

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs |  4 +--
 osu.Game/Overlays/Direct/DirectListPanel.cs |  4 +--
 osu.Game/Overlays/Direct/FilterControl.cs   |  5 ++--
 osu.Game/Overlays/DirectOverlay.cs          | 28 ++++++++++-----------
 4 files changed, 20 insertions(+), 21 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 63298052c3..4be68157d7 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -127,7 +127,7 @@ namespace osu.Game.Overlays.Direct
                                             {
                                                 new OsuSpriteText
                                                 {
-                                                    Text = @"mapped by ",
+                                                    Text = "mapped by ",
                                                     TextSize = 14,
                                                     Shadow = false,
                                                     Colour = colours.Gray5,
@@ -150,7 +150,7 @@ namespace osu.Game.Overlays.Direct
                                             {
                                                 new OsuSpriteText
                                                 {
-                                                    Text = $@"from {SetInfo.Metadata.Source}",
+                                                    Text = $"from {SetInfo.Metadata.Source}",
                                                     TextSize = 14,
                                                     Shadow = false,
                                                     Colour = colours.Gray5,
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 02930a9e7b..48636a5228 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -119,7 +119,7 @@ namespace osu.Game.Overlays.Direct
                                     {
                                         new OsuSpriteText
                                         {
-                                            Text = @"mapped by ",
+                                            Text = "mapped by ",
                                             TextSize = 14,
                                         },
                                         new OsuSpriteText
@@ -132,7 +132,7 @@ namespace osu.Game.Overlays.Direct
                                 },
                                 new OsuSpriteText
                                 {
-                                    Text = $@"from {SetInfo.Metadata.Source}",
+                                    Text = $"from {SetInfo.Metadata.Source}",
                                     Anchor = Anchor.TopRight,
                                     Origin = Anchor.TopRight,
                                     TextSize = 14,
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 7d8a4a6a85..031ca997b8 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -51,8 +51,7 @@ namespace osu.Game.Overlays.Direct
                 tabStrip = new Box
                 {
                     Anchor = Anchor.BottomLeft,
-                    Origin = Anchor.BottomLeft,
-                    Position = new Vector2(0f, 1f),
+                    Origin = Anchor.TopLeft,
                     RelativeSizeAxes = Axes.X,
                     Height = 1,
                 },
@@ -87,7 +86,7 @@ namespace osu.Game.Overlays.Direct
                     Origin = Anchor.TopRight,
                     Spacing = new Vector2(10f, 0f),
                     Direction = FillDirection.Horizontal,
-                    Margin = new MarginPadding { Top = Height - SlimEnumDropdown<DirectTab>.HEIGHT - padding, Right = DirectOverlay.WIDTH_PADDING },
+                    Margin = new MarginPadding { Top = HEIGHT - SlimEnumDropdown<DirectTab>.HEIGHT - padding, Right = DirectOverlay.WIDTH_PADDING },
                     Children = new Drawable[]
                     {
                         new FillFlowContainer
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 8fdc591371..5485ea3c15 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Overlays
         private const float panel_padding = 10f;
 
         private readonly FilterControl filter;
-        private readonly FillFlowContainer resultCountsContainer;
+        private readonly FillFlowContainer resultCountsContainer;
         private readonly OsuSpriteText resultCountsText;
         private readonly FillFlowContainer<DirectPanel> panels;
 
@@ -115,7 +115,7 @@ namespace osu.Game.Overlays
                                             {
                                                 new OsuSpriteText
                                                 {
-                                                    Text = @"Found ",
+                                                    Text = "Found ",
                                                     TextSize = 15,
                                                 },
                                                 resultCountsText = new OsuSpriteText
@@ -164,18 +164,18 @@ namespace osu.Game.Overlays
             resultCountsContainer.Colour = colours.Yellow;
         }
 
-        private void updateResultCounts()
-        {
-            resultCountsContainer.FadeTo(ResultCounts == null ? 0f : 1f, 200, EasingTypes.Out);
-            if (ResultCounts == null) return;
-
-            resultCountsText.Text = pluralize(@"Artist", ResultCounts.Artists) + ", " +
-                                    pluralize(@"Song", ResultCounts.Songs) + ", " +
-                                    pluralize(@"Tag", ResultCounts.Tags);
-        }
-
-        private string pluralize(string prefix, int value)
-        {
+        private void updateResultCounts()
+        {
+            resultCountsContainer.FadeTo(ResultCounts == null ? 0f : 1f, 200, EasingTypes.Out);
+            if (ResultCounts == null) return;
+
+            resultCountsText.Text = pluralize("Artist", ResultCounts.Artists) + ", " +
+                                    pluralize("Song", ResultCounts.Songs) + ", " +
+                                    pluralize("Tag", ResultCounts.Tags);
+        }
+
+        private string pluralize(string prefix, int value)
+        {
             return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
         }
 

From 4490596f5f171f987b0b8a88a8c1f4a93adf5b73 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 02:37:27 -0300
Subject: [PATCH 148/179] Keep one object per file

---
 .../Tests/TestCaseDirect.cs                   |  2 +-
 osu.Game/Database/RankStatus.cs               | 23 ++++++++
 osu.Game/Overlays/Direct/FilterControl.cs     | 55 +++----------------
 osu.Game/Overlays/DirectOverlay.cs            | 36 +++++++++---
 osu.Game/osu.Game.csproj                      |  1 +
 5 files changed, 62 insertions(+), 55 deletions(-)
 create mode 100644 osu.Game/Database/RankStatus.cs

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
index c1f2307f20..4ddf361087 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -25,7 +25,7 @@ namespace osu.Desktop.VisualTests.Tests
             newBeatmaps();
 
             AddStep(@"toggle", direct.ToggleVisibility);
-            AddStep(@"result counts", () => direct.ResultCounts = new ResultCounts(1, 4, 13));
+            AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13));
         }
 
         [BackgroundDependencyLoader]
diff --git a/osu.Game/Database/RankStatus.cs b/osu.Game/Database/RankStatus.cs
new file mode 100644
index 0000000000..b333292a07
--- /dev/null
+++ b/osu.Game/Database/RankStatus.cs
@@ -0,0 +1,23 @@
+// 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.ComponentModel;
+
+namespace osu.Game.Database
+{
+    public enum RankStatus
+    {
+    	Any = 7,
+    	[Description("Ranked & Approved")]
+    	RankedApproved = 0,
+    	Approved = 1,
+    	Loved = 8,
+    	Favourites = 2,
+    	[Description("Mod Requests")]
+    	ModRequests = 3,
+    	Pending = 4,
+    	Graveyard = 5,
+    	[Description("My Maps")]
+    	MyMaps = 6,
+    }
+}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 031ca997b8..735e14b8c1 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -1,7 +1,6 @@
 // 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.ComponentModel;
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
@@ -14,8 +13,6 @@ using osu.Game.Database;
 using osu.Game.Graphics;
 using osu.Game.Graphics.UserInterface;
 
-using Container = osu.Framework.Graphics.Containers.Container;
-
 namespace osu.Game.Overlays.Direct
 {
     public class FilterControl : Container
@@ -30,7 +27,7 @@ namespace osu.Game.Overlays.Direct
         public readonly SearchTextBox Search;
         public readonly SortTabControl SortTabs;
         public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
-        public readonly Bindable<PanelDisplayStyle> DisplayStyle = new Bindable<PanelDisplayStyle>();
+        public readonly Bindable<DirectOverlay.PanelDisplayStyle> DisplayStyle = new Bindable<DirectOverlay.PanelDisplayStyle>();
 
         protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || RankStatusDropdown.Contains(screenSpacePos);
 
@@ -38,7 +35,7 @@ namespace osu.Game.Overlays.Direct
         {
             RelativeSizeAxes = Axes.X;
             Height = HEIGHT;
-            DisplayStyle.Value = PanelDisplayStyle.Grid;
+            DisplayStyle.Value = DirectOverlay.PanelDisplayStyle.Grid;
 
             Children = new Drawable[]
             {
@@ -96,8 +93,8 @@ namespace osu.Game.Overlays.Direct
                             Direction = FillDirection.Horizontal,
                             Children = new[]
                             {
-                                new DisplayStyleToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle),
-                                new DisplayStyleToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle),
+                                new DisplayStyleToggleButton(FontAwesome.fa_th_large, DirectOverlay.PanelDisplayStyle.Grid, DisplayStyle),
+                                new DisplayStyleToggleButton(FontAwesome.fa_list_ul, DirectOverlay.PanelDisplayStyle.List, DisplayStyle),
                             },
                         },
                         RankStatusDropdown = new SlimEnumDropdown<RankStatus>
@@ -195,10 +192,10 @@ namespace osu.Game.Overlays.Direct
         private class DisplayStyleToggleButton : ClickableContainer
         {
             private readonly TextAwesome icon;
-            private readonly PanelDisplayStyle style;
-            private readonly Bindable<PanelDisplayStyle> bindable;
+            private readonly DirectOverlay.PanelDisplayStyle style;
+            private readonly Bindable<DirectOverlay.PanelDisplayStyle> bindable;
 
-            public DisplayStyleToggleButton(FontAwesome icon, PanelDisplayStyle style, Bindable<PanelDisplayStyle> bindable)
+            public DisplayStyleToggleButton(FontAwesome icon, DirectOverlay.PanelDisplayStyle style, Bindable<DirectOverlay.PanelDisplayStyle> bindable)
             {
                 this.bindable = bindable;
                 this.style = style;
@@ -222,7 +219,7 @@ namespace osu.Game.Overlays.Direct
                 Action = () => bindable.Value = this.style;
             }
 
-            private void Bindable_ValueChanged(PanelDisplayStyle style)
+            private void Bindable_ValueChanged(DirectOverlay.PanelDisplayStyle style)
             {
                 icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100);
             }
@@ -233,40 +230,4 @@ namespace osu.Game.Overlays.Direct
             }
         }
     }
-
-    public class ResultCounts
-    {
-        public readonly int Artists;
-        public readonly int Songs;
-        public readonly int Tags;
-
-        public ResultCounts(int artists, int songs, int tags)
-        {
-            Artists = artists;
-            Songs = songs;
-            Tags = tags;
-        }
-    }
-
-    public enum RankStatus
-    {
-        Any,
-        [Description("Ranked & Approved")]
-        RankedApproved,
-        Approved,
-        Loved,
-        Favourites,
-        [Description("Mod Requests")]
-        ModRequests,
-        Pending,
-        Graveyard,
-        [Description("My Maps")]
-        MyMaps,
-    }
-
-    public enum PanelDisplayStyle
-    {
-        Grid,
-        List,
-    }
 }
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 5485ea3c15..c1a0e1db79 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -15,6 +15,8 @@ using osu.Game.Graphics.Backgrounds;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Overlays.Direct;
 
+using Container = osu.Framework.Graphics.Containers.Container;
+
 namespace osu.Game.Overlays
 {
     public class DirectOverlay : WaveOverlayContainer
@@ -41,12 +43,12 @@ namespace osu.Game.Overlays
         }
 
         private ResultCounts resultCounts;
-        public ResultCounts ResultCounts
+        public ResultCounts ResultAmounts
         {
             get { return resultCounts; }
             set
             {
-                if (value == ResultCounts) return;
+                if (value == ResultAmounts) return;
                 resultCounts = value;
 
                 updateResultCounts();
@@ -166,12 +168,12 @@ namespace osu.Game.Overlays
 
         private void updateResultCounts()
         {
-            resultCountsContainer.FadeTo(ResultCounts == null ? 0f : 1f, 200, EasingTypes.Out);
-            if (ResultCounts == null) return;
+            resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, EasingTypes.Out);
+            if (ResultAmounts == null) return;
 
-            resultCountsText.Text = pluralize("Artist", ResultCounts.Artists) + ", " +
-                                    pluralize("Song", ResultCounts.Songs) + ", " +
-                                    pluralize("Tag", ResultCounts.Tags);
+            resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " +
+                                    pluralize("Song", ResultAmounts.Songs) + ", " +
+                                    pluralize("Tag", ResultAmounts.Tags);
         }
 
         private string pluralize(string prefix, int value)
@@ -204,5 +206,25 @@ namespace osu.Game.Overlays
 
             filter.Search.HoldFocus = false;
         }
+
+        public class ResultCounts
+        {
+        	public readonly int Artists;
+        	public readonly int Songs;
+        	public readonly int Tags;
+
+        	public ResultCounts(int artists, int songs, int tags)
+        	{
+        		Artists = artists;
+        		Songs = songs;
+        		Tags = tags;
+        	}
+        }
+
+        public enum PanelDisplayStyle
+        {
+	        Grid,
+            List,
+        }
     }
 }
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1fb2658679..f6779c2701 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -439,6 +439,7 @@
     <Compile Include="Database\BeatmapOnlineInfo.cs" />
     <Compile Include="Overlays\Direct\SlimEnumDropdown.cs" />
     <Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" />
+    <Compile Include="Database\RankStatus.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

From e59c1879a272b975bd70c72529412bb333371e9e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 02:38:55 -0300
Subject: [PATCH 149/179] Remove tabs

---
 osu.Game/Database/RankStatus.cs    | 24 ++++++++++++------------
 osu.Game/Overlays/DirectOverlay.cs | 18 +++++++++---------
 2 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/osu.Game/Database/RankStatus.cs b/osu.Game/Database/RankStatus.cs
index b333292a07..d18949f5bd 100644
--- a/osu.Game/Database/RankStatus.cs
+++ b/osu.Game/Database/RankStatus.cs
@@ -7,17 +7,17 @@ namespace osu.Game.Database
 {
     public enum RankStatus
     {
-    	Any = 7,
-    	[Description("Ranked & Approved")]
-    	RankedApproved = 0,
-    	Approved = 1,
-    	Loved = 8,
-    	Favourites = 2,
-    	[Description("Mod Requests")]
-    	ModRequests = 3,
-    	Pending = 4,
-    	Graveyard = 5,
-    	[Description("My Maps")]
-    	MyMaps = 6,
+        Any = 7,
+        [Description("Ranked & Approved")]
+        RankedApproved = 0,
+        Approved = 1,
+        Loved = 8,
+        Favourites = 2,
+        [Description("Mod Requests")]
+        ModRequests = 3,
+        Pending = 4,
+        Graveyard = 5,
+        [Description("My Maps")]
+        MyMaps = 6,
     }
 }
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index c1a0e1db79..f9c76cf80f 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -209,16 +209,16 @@ namespace osu.Game.Overlays
 
         public class ResultCounts
         {
-        	public readonly int Artists;
-        	public readonly int Songs;
-        	public readonly int Tags;
+            public readonly int Artists;
+            public readonly int Songs;
+            public readonly int Tags;
 
-        	public ResultCounts(int artists, int songs, int tags)
-        	{
-        		Artists = artists;
-        		Songs = songs;
-        		Tags = tags;
-        	}
+            public ResultCounts(int artists, int songs, int tags)
+            {
+                Artists = artists;
+                Songs = songs;
+                Tags = tags;
+            }
         }
 
         public enum PanelDisplayStyle

From 72c995921587a38d3abb6ea664e5c549adabe442 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 02:40:34 -0300
Subject: [PATCH 150/179] resultCounts -> ResultAmounts

---
 osu.Game/Overlays/DirectOverlay.cs | 460 ++++++++++++++---------------
 1 file changed, 230 insertions(+), 230 deletions(-)

diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 1f0905bc7c..7517ff2066 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -1,230 +1,230 @@
-// 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.Collections.Generic;
-using System.Linq;
-using OpenTK;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input;
-using osu.Game.Database;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Backgrounds;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Overlays.Direct;
-
-using Container = osu.Framework.Graphics.Containers.Container;
-
-namespace osu.Game.Overlays
-{
-    public class DirectOverlay : WaveOverlayContainer
-    {
-        public static readonly int WIDTH_PADDING = 80;
-        private const float panel_padding = 10f;
-
-        private readonly FilterControl filter;
-        private readonly FillFlowContainer resultCountsContainer;
-        private readonly OsuSpriteText resultCountsText;
-        private readonly FillFlowContainer<DirectPanel> panels;
-
-        private IEnumerable<BeatmapSetInfo> beatmapSets;
-        public IEnumerable<BeatmapSetInfo> BeatmapSets
-        {
-            get { return beatmapSets; }
-            set
-            {
-                if (beatmapSets?.Equals(value) ?? false) return;
-                beatmapSets = value;
-
-                recreatePanels(filter.DisplayStyle.Value);
-            }
-        }
-
-        private ResultCounts resultCounts;
-        public ResultCounts ResultAmounts
-        {
-            get { return resultCounts; }
-            set
-            {
-                if (value == ResultAmounts) return;
-                resultCounts = value;
-
-                updateResultCounts();
-            }
-        }
-
-        public DirectOverlay()
-        {
-            RelativeSizeAxes = Axes.Both;
-
-            // osu!direct colours are not part of the standard palette
-
-            FirstWaveColour = OsuColour.FromHex(@"19b0e2");
-            SecondWaveColour = OsuColour.FromHex(@"2280a2");
-            ThirdWaveColour = OsuColour.FromHex(@"005774");
-            FourthWaveColour = OsuColour.FromHex(@"003a4e");
-
-            Header header;
-            Children = new Drawable[]
-            {
-                new Box
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Colour = OsuColour.FromHex(@"485e74"),
-                },
-                new Container
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Masking = true,
-                    Children = new[]
-                    {
-                        new Triangles
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                            TriangleScale = 5,
-                            ColourLight = OsuColour.FromHex(@"465b71"),
-                            ColourDark = OsuColour.FromHex(@"3f5265"),
-                        },
-                    },
-                },
-                new Container
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
-                    Children = new[]
-                    {
-                        new ScrollContainer
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                            ScrollDraggerVisible = false,
-                            Children = new Drawable[]
-                            {
-                                new FillFlowContainer
-                                {
-                                    RelativeSizeAxes = Axes.X,
-                                    AutoSizeAxes = Axes.Y,
-                                    Direction = FillDirection.Vertical,
-                                    Children = new Drawable[]
-                                    {
-                                        resultCountsContainer = new FillFlowContainer
-                                        {
-                                            AutoSizeAxes = Axes.Both,
-                                            Direction = FillDirection.Horizontal,
-                                            Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
-                                            Children = new Drawable[]
-                                            {
-                                                new OsuSpriteText
-                                                {
-                                                    Text = "Found ",
-                                                    TextSize = 15,
-                                                },
-                                                resultCountsText = new OsuSpriteText
-                                                {
-                                                    TextSize = 15,
-                                                    Font = @"Exo2.0-Bold",
-                                                },
-                                            }
-                                        },
-                                        panels = new FillFlowContainer<DirectPanel>
-                                        {
-                                            RelativeSizeAxes = Axes.X,
-                                            AutoSizeAxes = Axes.Y,
-                                            Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
-                                            Spacing = new Vector2(panel_padding),
-                                        },
-                                    },
-                                },
-                            },
-                        },
-                    },
-                },
-                filter = new FilterControl
-                {
-                    RelativeSizeAxes = Axes.X,
-                    Margin = new MarginPadding { Top = Header.HEIGHT },
-                },
-                header = new Header
-                {
-                    RelativeSizeAxes = Axes.X,
-                },
-            };
-
-            header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; };
-
-            filter.Search.Exit = Hide;
-            filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
-            filter.DisplayStyle.ValueChanged += recreatePanels;
-
-            updateResultCounts();
-        }
-
-        [BackgroundDependencyLoader]
-        private void load(OsuColour colours)
-        {
-            resultCountsContainer.Colour = colours.Yellow;
-        }
-
-        private void updateResultCounts()
-        {
-            resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, EasingTypes.Out);
-            if (ResultAmounts == null) return;
-
-            resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " +
-                                    pluralize("Song", ResultAmounts.Songs) + ", " +
-                                    pluralize("Tag", ResultAmounts.Tags);
-        }
-
-        private string pluralize(string prefix, int value)
-        {
-            return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
-        }
-
-        private void recreatePanels(PanelDisplayStyle displayStyle)
-        {
-            if (BeatmapSets == null) return;
-            panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
-        }
-
-        protected override bool OnFocus(InputState state)
-        {
-            filter.Search.TriggerFocus();
-            return false;
-        }
-
-        protected override void PopIn()
-        {
-            base.PopIn();
-
-            filter.Search.HoldFocus = true;
-        }
-
-        protected override void PopOut()
-        {
-            base.PopOut();
-
-            filter.Search.HoldFocus = false;
-        }
-
-        public class ResultCounts
-        {
-            public readonly int Artists;
-            public readonly int Songs;
-            public readonly int Tags;
-
-            public ResultCounts(int artists, int songs, int tags)
-            {
-                Artists = artists;
-                Songs = songs;
-                Tags = tags;
-            }
-        }
-
-        public enum PanelDisplayStyle
-        {
-	        Grid,
-            List,
-        }
-    }
-}
+// 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.Collections.Generic;
+using System.Linq;
+using OpenTK;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays.Direct;
+
+using Container = osu.Framework.Graphics.Containers.Container;
+
+namespace osu.Game.Overlays
+{
+    public class DirectOverlay : WaveOverlayContainer
+    {
+        public static readonly int WIDTH_PADDING = 80;
+        private const float panel_padding = 10f;
+
+        private readonly FilterControl filter;
+        private readonly FillFlowContainer resultCountsContainer;
+        private readonly OsuSpriteText resultCountsText;
+        private readonly FillFlowContainer<DirectPanel> panels;
+
+        private IEnumerable<BeatmapSetInfo> beatmapSets;
+        public IEnumerable<BeatmapSetInfo> BeatmapSets
+        {
+            get { return beatmapSets; }
+            set
+            {
+                if (beatmapSets?.Equals(value) ?? false) return;
+                beatmapSets = value;
+
+                recreatePanels(filter.DisplayStyle.Value);
+            }
+        }
+
+        private ResultCounts resultAmounts;
+        public ResultCounts ResultAmounts
+        {
+            get { return resultAmounts; }
+            set
+            {
+                if (value == ResultAmounts) return;
+                resultAmounts = value;
+
+                updateResultCounts();
+            }
+        }
+
+        public DirectOverlay()
+        {
+            RelativeSizeAxes = Axes.Both;
+
+            // osu!direct colours are not part of the standard palette
+
+            FirstWaveColour = OsuColour.FromHex(@"19b0e2");
+            SecondWaveColour = OsuColour.FromHex(@"2280a2");
+            ThirdWaveColour = OsuColour.FromHex(@"005774");
+            FourthWaveColour = OsuColour.FromHex(@"003a4e");
+
+            Header header;
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"485e74"),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Masking = true,
+                    Children = new[]
+                    {
+                        new Triangles
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            TriangleScale = 5,
+                            ColourLight = OsuColour.FromHex(@"465b71"),
+                            ColourDark = OsuColour.FromHex(@"3f5265"),
+                        },
+                    },
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
+                    Children = new[]
+                    {
+                        new ScrollContainer
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            ScrollDraggerVisible = false,
+                            Children = new Drawable[]
+                            {
+                                new FillFlowContainer
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Direction = FillDirection.Vertical,
+                                    Children = new Drawable[]
+                                    {
+                                        resultCountsContainer = new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.Both,
+                                            Direction = FillDirection.Horizontal,
+                                            Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
+                                            Children = new Drawable[]
+                                            {
+                                                new OsuSpriteText
+                                                {
+                                                    Text = "Found ",
+                                                    TextSize = 15,
+                                                },
+                                                resultCountsText = new OsuSpriteText
+                                                {
+                                                    TextSize = 15,
+                                                    Font = @"Exo2.0-Bold",
+                                                },
+                                            }
+                                        },
+                                        panels = new FillFlowContainer<DirectPanel>
+                                        {
+                                            RelativeSizeAxes = Axes.X,
+                                            AutoSizeAxes = Axes.Y,
+                                            Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
+                                            Spacing = new Vector2(panel_padding),
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+                filter = new FilterControl
+                {
+                    RelativeSizeAxes = Axes.X,
+                    Margin = new MarginPadding { Top = Header.HEIGHT },
+                },
+                header = new Header
+                {
+                    RelativeSizeAxes = Axes.X,
+                },
+            };
+
+            header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; };
+
+            filter.Search.Exit = Hide;
+            filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
+            filter.DisplayStyle.ValueChanged += recreatePanels;
+
+            updateResultCounts();
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            resultCountsContainer.Colour = colours.Yellow;
+        }
+
+        private void updateResultCounts()
+        {
+            resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, EasingTypes.Out);
+            if (ResultAmounts == null) return;
+
+            resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " +
+                                    pluralize("Song", ResultAmounts.Songs) + ", " +
+                                    pluralize("Tag", ResultAmounts.Tags);
+        }
+
+        private string pluralize(string prefix, int value)
+        {
+            return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
+        }
+
+        private void recreatePanels(PanelDisplayStyle displayStyle)
+        {
+            if (BeatmapSets == null) return;
+            panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
+        }
+
+        protected override bool OnFocus(InputState state)
+        {
+            filter.Search.TriggerFocus();
+            return false;
+        }
+
+        protected override void PopIn()
+        {
+            base.PopIn();
+
+            filter.Search.HoldFocus = true;
+        }
+
+        protected override void PopOut()
+        {
+            base.PopOut();
+
+            filter.Search.HoldFocus = false;
+        }
+
+        public class ResultCounts
+        {
+            public readonly int Artists;
+            public readonly int Songs;
+            public readonly int Tags;
+
+            public ResultCounts(int artists, int songs, int tags)
+            {
+                Artists = artists;
+                Songs = songs;
+                Tags = tags;
+            }
+        }
+
+        public enum PanelDisplayStyle
+        {
+	        Grid,
+            List,
+        }
+    }
+}

From 4224143136f4136ada12c60e05f4e2d499051b9c Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 14:53:28 +0900
Subject: [PATCH 151/179] Add faint kiai explosion on the hit marker.

---
 .../UI/KiaiHitExplosion.cs                    | 68 +++++++++++++++++++
 osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs  | 16 ++++-
 .../osu.Game.Rulesets.Taiko.csproj            |  1 +
 3 files changed, 84 insertions(+), 1 deletion(-)
 create mode 100644 osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs

diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
new file mode 100644
index 0000000000..62e4165ce4
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
@@ -0,0 +1,68 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+    public class KiaiHitExplosion : CircularContainer
+    {
+        public readonly TaikoJudgement Judgement;
+
+        private readonly bool isRim;
+
+        public KiaiHitExplosion(TaikoJudgement judgement, bool isRim)
+        {
+            this.isRim = isRim;
+
+            Judgement = judgement;
+
+            Anchor = Anchor.Centre;
+            Origin = Anchor.Centre;
+
+            RelativeSizeAxes = Axes.Y;
+            Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER, 1);
+
+            Masking = true;
+            Alpha = 0.15f;
+
+            Children = new[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Alpha = 0,
+                    AlwaysPresent = true
+                }
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Glow,
+                Colour = isRim ? colours.BlueDarker : colours.PinkDarker,
+                Radius = 60,
+            };
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            ScaleTo(new Vector2(1, 3f), 500, EasingTypes.OutQuint);
+            FadeOut(250);
+
+            Expire();
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index d6b2b26b7c..8f2d89dd05 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         protected override Container<Drawable> Content => hitObjectContainer;
 
         private readonly Container<HitExplosion> hitExplosionContainer;
+        private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
         private readonly Container<DrawableBarLine> barLineContainer;
         private readonly Container<DrawableTaikoJudgement> judgementContainer;
 
@@ -117,6 +118,13 @@ namespace osu.Game.Rulesets.Taiko.UI
                                         },
                                     }
                                 },
+                                kiaiExplosionContainer = new Container<KiaiHitExplosion>
+                                {
+                                    Name = "Kiai hit explosions",
+                                    RelativeSizeAxes = Axes.Y,
+                                    Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
+                                    BlendingMode = BlendingMode.Additive
+                                },
                                 judgementContainer = new Container<DrawableTaikoJudgement>
                                 {
                                     Name = "Judgements",
@@ -207,6 +215,8 @@ namespace osu.Game.Rulesets.Taiko.UI
             if (!wasHit)
                 return;
 
+            bool isRim = judgedObject.HitObject is RimHit;
+
             if (!secondHit)
             {
                 if (judgedObject.X >= -0.05f && !(judgedObject is DrawableSwell))
@@ -215,7 +225,11 @@ namespace osu.Game.Rulesets.Taiko.UI
                     topLevelHitContainer.Add(judgedObject.CreateProxy());
                 }
 
-                hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement, judgedObject is DrawableRimHit));
+                hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement, isRim));
+
+                if (judgedObject.HitObject.Kiai)
+                    kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject.Judgement, isRim));
+
             }
             else
                 hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index 983dc72d9e..8d6fcb503c 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -87,6 +87,7 @@
     <Compile Include="Scoring\TaikoScoreProcessor.cs" />
     <Compile Include="UI\HitTarget.cs" />
     <Compile Include="UI\InputDrum.cs" />
+    <Compile Include="UI\KiaiHitExplosion.cs" />
     <Compile Include="UI\DrawableTaikoJudgement.cs" />
     <Compile Include="UI\HitExplosion.cs" />
     <Compile Include="UI\TaikoHitRenderer.cs" />

From a25c504965114a69cb3a30bc2a6adc6e8ba805ff Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 02:41:51 -0300
Subject: [PATCH 152/179] CI fixes

---
 .../Tests/TestCaseDirect.cs                   |   1 -
 osu.Game/Database/RankStatus.cs               |  30 +-
 osu.Game/Overlays/DirectOverlay.cs            | 460 +++++++++---------
 3 files changed, 245 insertions(+), 246 deletions(-)

diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
index 4ddf361087..4cda14559f 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -6,7 +6,6 @@ using osu.Framework.Allocation;
 using osu.Framework.Testing;
 using osu.Game.Database;
 using osu.Game.Overlays;
-using osu.Game.Overlays.Direct;
 
 namespace osu.Desktop.VisualTests.Tests
 {
diff --git a/osu.Game/Database/RankStatus.cs b/osu.Game/Database/RankStatus.cs
index d18949f5bd..f2a7d67a40 100644
--- a/osu.Game/Database/RankStatus.cs
+++ b/osu.Game/Database/RankStatus.cs
@@ -1,23 +1,23 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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.ComponentModel;
 
 namespace osu.Game.Database
 {
-    public enum RankStatus
-    {
-        Any = 7,
-        [Description("Ranked & Approved")]
-        RankedApproved = 0,
-        Approved = 1,
-        Loved = 8,
-        Favourites = 2,
-        [Description("Mod Requests")]
-        ModRequests = 3,
-        Pending = 4,
-        Graveyard = 5,
-        [Description("My Maps")]
+    public enum RankStatus
+    {
+        Any = 7,
+        [Description("Ranked & Approved")]
+        RankedApproved = 0,
+        Approved = 1,
+        Loved = 8,
+        Favourites = 2,
+        [Description("Mod Requests")]
+        ModRequests = 3,
+        Pending = 4,
+        Graveyard = 5,
+        [Description("My Maps")]
         MyMaps = 6,
     }
 }
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 7517ff2066..0930c825b6 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -1,230 +1,230 @@
-// 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.Collections.Generic;
-using System.Linq;
-using OpenTK;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input;
-using osu.Game.Database;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Backgrounds;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Overlays.Direct;
-
-using Container = osu.Framework.Graphics.Containers.Container;
-
-namespace osu.Game.Overlays
-{
-    public class DirectOverlay : WaveOverlayContainer
-    {
-        public static readonly int WIDTH_PADDING = 80;
-        private const float panel_padding = 10f;
-
-        private readonly FilterControl filter;
-        private readonly FillFlowContainer resultCountsContainer;
-        private readonly OsuSpriteText resultCountsText;
-        private readonly FillFlowContainer<DirectPanel> panels;
-
-        private IEnumerable<BeatmapSetInfo> beatmapSets;
-        public IEnumerable<BeatmapSetInfo> BeatmapSets
-        {
-            get { return beatmapSets; }
-            set
-            {
-                if (beatmapSets?.Equals(value) ?? false) return;
-                beatmapSets = value;
-
-                recreatePanels(filter.DisplayStyle.Value);
-            }
-        }
-
-        private ResultCounts resultAmounts;
-        public ResultCounts ResultAmounts
-        {
-            get { return resultAmounts; }
-            set
-            {
-                if (value == ResultAmounts) return;
-                resultAmounts = value;
-
-                updateResultCounts();
-            }
-        }
-
-        public DirectOverlay()
-        {
-            RelativeSizeAxes = Axes.Both;
-
-            // osu!direct colours are not part of the standard palette
-
-            FirstWaveColour = OsuColour.FromHex(@"19b0e2");
-            SecondWaveColour = OsuColour.FromHex(@"2280a2");
-            ThirdWaveColour = OsuColour.FromHex(@"005774");
-            FourthWaveColour = OsuColour.FromHex(@"003a4e");
-
-            Header header;
-            Children = new Drawable[]
-            {
-                new Box
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Colour = OsuColour.FromHex(@"485e74"),
-                },
-                new Container
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Masking = true,
-                    Children = new[]
-                    {
-                        new Triangles
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                            TriangleScale = 5,
-                            ColourLight = OsuColour.FromHex(@"465b71"),
-                            ColourDark = OsuColour.FromHex(@"3f5265"),
-                        },
-                    },
-                },
-                new Container
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
-                    Children = new[]
-                    {
-                        new ScrollContainer
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                            ScrollDraggerVisible = false,
-                            Children = new Drawable[]
-                            {
-                                new FillFlowContainer
-                                {
-                                    RelativeSizeAxes = Axes.X,
-                                    AutoSizeAxes = Axes.Y,
-                                    Direction = FillDirection.Vertical,
-                                    Children = new Drawable[]
-                                    {
-                                        resultCountsContainer = new FillFlowContainer
-                                        {
-                                            AutoSizeAxes = Axes.Both,
-                                            Direction = FillDirection.Horizontal,
-                                            Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
-                                            Children = new Drawable[]
-                                            {
-                                                new OsuSpriteText
-                                                {
-                                                    Text = "Found ",
-                                                    TextSize = 15,
-                                                },
-                                                resultCountsText = new OsuSpriteText
-                                                {
-                                                    TextSize = 15,
-                                                    Font = @"Exo2.0-Bold",
-                                                },
-                                            }
-                                        },
-                                        panels = new FillFlowContainer<DirectPanel>
-                                        {
-                                            RelativeSizeAxes = Axes.X,
-                                            AutoSizeAxes = Axes.Y,
-                                            Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
-                                            Spacing = new Vector2(panel_padding),
-                                        },
-                                    },
-                                },
-                            },
-                        },
-                    },
-                },
-                filter = new FilterControl
-                {
-                    RelativeSizeAxes = Axes.X,
-                    Margin = new MarginPadding { Top = Header.HEIGHT },
-                },
-                header = new Header
-                {
-                    RelativeSizeAxes = Axes.X,
-                },
-            };
-
-            header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; };
-
-            filter.Search.Exit = Hide;
-            filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
-            filter.DisplayStyle.ValueChanged += recreatePanels;
-
-            updateResultCounts();
-        }
-
-        [BackgroundDependencyLoader]
-        private void load(OsuColour colours)
-        {
-            resultCountsContainer.Colour = colours.Yellow;
-        }
-
-        private void updateResultCounts()
-        {
-            resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, EasingTypes.Out);
-            if (ResultAmounts == null) return;
-
-            resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " +
-                                    pluralize("Song", ResultAmounts.Songs) + ", " +
-                                    pluralize("Tag", ResultAmounts.Tags);
-        }
-
-        private string pluralize(string prefix, int value)
-        {
-            return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
-        }
-
-        private void recreatePanels(PanelDisplayStyle displayStyle)
-        {
-            if (BeatmapSets == null) return;
-            panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
-        }
-
-        protected override bool OnFocus(InputState state)
-        {
-            filter.Search.TriggerFocus();
-            return false;
-        }
-
-        protected override void PopIn()
-        {
-            base.PopIn();
-
-            filter.Search.HoldFocus = true;
-        }
-
-        protected override void PopOut()
-        {
-            base.PopOut();
-
-            filter.Search.HoldFocus = false;
-        }
-
-        public class ResultCounts
-        {
-            public readonly int Artists;
-            public readonly int Songs;
-            public readonly int Tags;
-
-            public ResultCounts(int artists, int songs, int tags)
-            {
-                Artists = artists;
-                Songs = songs;
-                Tags = tags;
-            }
-        }
-
-        public enum PanelDisplayStyle
-        {
-	        Grid,
-            List,
-        }
-    }
-}
+// 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.Collections.Generic;
+using System.Linq;
+using OpenTK;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays.Direct;
+
+using Container = osu.Framework.Graphics.Containers.Container;
+
+namespace osu.Game.Overlays
+{
+    public class DirectOverlay : WaveOverlayContainer
+    {
+        public static readonly int WIDTH_PADDING = 80;
+        private const float panel_padding = 10f;
+
+        private readonly FilterControl filter;
+        private readonly FillFlowContainer resultCountsContainer;
+        private readonly OsuSpriteText resultCountsText;
+        private readonly FillFlowContainer<DirectPanel> panels;
+
+        private IEnumerable<BeatmapSetInfo> beatmapSets;
+        public IEnumerable<BeatmapSetInfo> BeatmapSets
+        {
+            get { return beatmapSets; }
+            set
+            {
+                if (beatmapSets?.Equals(value) ?? false) return;
+                beatmapSets = value;
+
+                recreatePanels(filter.DisplayStyle.Value);
+            }
+        }
+
+        private ResultCounts resultAmounts;
+        public ResultCounts ResultAmounts
+        {
+            get { return resultAmounts; }
+            set
+            {
+                if (value == ResultAmounts) return;
+                resultAmounts = value;
+
+                updateResultCounts();
+            }
+        }
+
+        public DirectOverlay()
+        {
+            RelativeSizeAxes = Axes.Both;
+
+            // osu!direct colours are not part of the standard palette
+
+            FirstWaveColour = OsuColour.FromHex(@"19b0e2");
+            SecondWaveColour = OsuColour.FromHex(@"2280a2");
+            ThirdWaveColour = OsuColour.FromHex(@"005774");
+            FourthWaveColour = OsuColour.FromHex(@"003a4e");
+
+            Header header;
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"485e74"),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Masking = true,
+                    Children = new[]
+                    {
+                        new Triangles
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            TriangleScale = 5,
+                            ColourLight = OsuColour.FromHex(@"465b71"),
+                            ColourDark = OsuColour.FromHex(@"3f5265"),
+                        },
+                    },
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
+                    Children = new[]
+                    {
+                        new ScrollContainer
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            ScrollDraggerVisible = false,
+                            Children = new Drawable[]
+                            {
+                                new FillFlowContainer
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Direction = FillDirection.Vertical,
+                                    Children = new Drawable[]
+                                    {
+                                        resultCountsContainer = new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.Both,
+                                            Direction = FillDirection.Horizontal,
+                                            Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
+                                            Children = new Drawable[]
+                                            {
+                                                new OsuSpriteText
+                                                {
+                                                    Text = "Found ",
+                                                    TextSize = 15,
+                                                },
+                                                resultCountsText = new OsuSpriteText
+                                                {
+                                                    TextSize = 15,
+                                                    Font = @"Exo2.0-Bold",
+                                                },
+                                            }
+                                        },
+                                        panels = new FillFlowContainer<DirectPanel>
+                                        {
+                                            RelativeSizeAxes = Axes.X,
+                                            AutoSizeAxes = Axes.Y,
+                                            Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
+                                            Spacing = new Vector2(panel_padding),
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+                filter = new FilterControl
+                {
+                    RelativeSizeAxes = Axes.X,
+                    Margin = new MarginPadding { Top = Header.HEIGHT },
+                },
+                header = new Header
+                {
+                    RelativeSizeAxes = Axes.X,
+                },
+            };
+
+            header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; };
+
+            filter.Search.Exit = Hide;
+            filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
+            filter.DisplayStyle.ValueChanged += recreatePanels;
+
+            updateResultCounts();
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            resultCountsContainer.Colour = colours.Yellow;
+        }
+
+        private void updateResultCounts()
+        {
+            resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, EasingTypes.Out);
+            if (ResultAmounts == null) return;
+
+            resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " +
+                                    pluralize("Song", ResultAmounts.Songs) + ", " +
+                                    pluralize("Tag", ResultAmounts.Tags);
+        }
+
+        private string pluralize(string prefix, int value)
+        {
+            return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
+        }
+
+        private void recreatePanels(PanelDisplayStyle displayStyle)
+        {
+            if (BeatmapSets == null) return;
+            panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
+        }
+
+        protected override bool OnFocus(InputState state)
+        {
+            filter.Search.TriggerFocus();
+            return false;
+        }
+
+        protected override void PopIn()
+        {
+            base.PopIn();
+
+            filter.Search.HoldFocus = true;
+        }
+
+        protected override void PopOut()
+        {
+            base.PopOut();
+
+            filter.Search.HoldFocus = false;
+        }
+
+        public class ResultCounts
+        {
+            public readonly int Artists;
+            public readonly int Songs;
+            public readonly int Tags;
+
+            public ResultCounts(int artists, int songs, int tags)
+            {
+                Artists = artists;
+                Songs = songs;
+                Tags = tags;
+            }
+        }
+
+        public enum PanelDisplayStyle
+        {
+            Grid,
+            List,
+        }
+    }
+}

From 445b469e47fe81acc9a99b2d2f2d375eec4ed1d3 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 15:22:46 +0900
Subject: [PATCH 153/179] Use ligher hue instead of white.

---
 .../Objects/Drawables/DrawableCentreHit.cs                   | 1 +
 .../Objects/Drawables/DrawableCentreHitStrong.cs             | 1 +
 osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs  | 1 +
 .../Objects/Drawables/DrawableRimHitStrong.cs                | 1 +
 .../Objects/Drawables/Pieces/CirclePiece.cs                  | 2 +-
 .../Objects/Drawables/Pieces/TaikoPiece.cs                   | 5 +++++
 6 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs
index 8bb78669ca..b2760e6914 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         private void load(OsuColour colours)
         {
             MainPiece.AccentColour = colours.PinkDarker;
+            MainPiece.KiaiFlashColour = colours.PinkLight;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs
index 434fb9377f..da2e4a5733 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         private void load(OsuColour colours)
         {
             MainPiece.AccentColour = colours.PinkDarker;
+            MainPiece.KiaiFlashColour = colours.PinkLight;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs
index 20e8d36105..cd5ceaa965 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         private void load(OsuColour colours)
         {
             MainPiece.AccentColour = colours.BlueDarker;
+            MainPiece.KiaiFlashColour = colours.BlueLight;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs
index 4b1bb62bab..c9387f6e72 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         private void load(OsuColour colours)
         {
             MainPiece.AccentColour = colours.BlueDarker;
+            MainPiece.KiaiFlashColour = colours.BlueLight;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index f182eb6993..6118e00e25 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
             if (beatIndex % (int)timingPoint.TimeSignature != 0)
                 return;
 
-            background.FadeEdgeEffectTo(Color4.White);
+            background.FadeEdgeEffectTo(KiaiFlashColour);
             using (BeginDelayedSequence(200))
                 background.FadeEdgeEffectTo(AccentColour, 500, EasingTypes.OutQuint);
         }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
index 5e7e9e6350..9ef9224942 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
@@ -20,6 +20,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
             set { accentColour = value; }
         }
 
+        /// <summary>
+        /// The colour to be flashed on a kiai beat.
+        /// </summary>
+        public Color4 KiaiFlashColour;
+
         private bool kiaiMode;
         /// <summary>
         /// Whether Kiai mode effects are enabled for this circle piece.

From c29f4b2ee86385c51b0bae0acbfca382dc9931c1 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 15:40:34 +0900
Subject: [PATCH 154/179] Fix weird glow + add small pre-beat transition.

---
 .../Objects/Drawables/Pieces/CirclePiece.cs           | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index 6118e00e25..992c10fa99 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
         public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f;
         public const float SYMBOL_BORDER = 8;
         public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER;
+        private const double pre_beat_transition_time = 50;
 
         /// <summary>
         /// The colour of the inner circle and outer glows.
@@ -65,6 +66,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
 
         public CirclePiece(bool isStrong = false)
         {
+            EarlyActivationMilliseconds = pre_beat_transition_time;
+
             AddInternal(new Drawable[]
             {
                 background = new CircularContainer
@@ -159,9 +162,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
             if (beatIndex % (int)timingPoint.TimeSignature != 0)
                 return;
 
-            background.FadeEdgeEffectTo(KiaiFlashColour);
-            using (BeginDelayedSequence(200))
-                background.FadeEdgeEffectTo(AccentColour, 500, EasingTypes.OutQuint);
+            double duration = timingPoint.BeatLength * (int)timingPoint.TimeSignature;
+
+            background.FadeEdgeEffectTo(KiaiFlashColour, pre_beat_transition_time, EasingTypes.OutQuint);
+            using (background.BeginDelayedSequence(pre_beat_transition_time))
+                background.FadeEdgeEffectTo(AccentColour, duration, EasingTypes.OutQuint);
         }
     }
 }
\ No newline at end of file

From 56fe97a147197bd4cba09a6f462829481fbfef25 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 15:48:27 +0900
Subject: [PATCH 155/179] Make kiai hit explosions slightly more prominent.

---
 osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
index 62e4165ce4..e0da3ed3db 100644
--- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.UI
             Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER, 1);
 
             Masking = true;
-            Alpha = 0.15f;
+            Alpha = 0.25f;
 
             Children = new[]
             {

From 7afa1766e17f50507bb93041ff9ca54c5f22c62e Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Wed, 24 May 2017 15:48:47 +0900
Subject: [PATCH 156/179] Make HitExplosion circular again, keep it masked to
 the stage.

---
 osu.Game.Rulesets.Taiko/UI/HitExplosion.cs   | 13 ++++++-------
 osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 10 +++++-----
 2 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
index 868f1cd9c0..c0c329c870 100644
--- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.UI
     /// <summary>
     /// A circle explodes from the hit target to indicate a hitobject has been hit.
     /// </summary>
-    internal class HitExplosion : Container
+    internal class HitExplosion : CircularContainer
     {
         public readonly TaikoJudgement Judgement;
 
@@ -30,11 +30,10 @@ namespace osu.Game.Rulesets.Taiko.UI
 
             Judgement = judgement;
 
-            RelativeSizeAxes = Axes.Y;
-            Width = TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_CIRCLE_DIAMETER;
+            Anchor = Anchor.Centre;
+            Origin = Anchor.Centre;
 
-            Anchor = Anchor.CentreLeft;
-            Origin = Anchor.CentreLeft;
+            Size = new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
 
             RelativePositionAxes = Axes.Both;
 
@@ -63,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         {
             base.LoadComplete();
 
-            ScaleTo(new Vector2(2f, 1), 1000, EasingTypes.OutQuint);
+            ScaleTo(3f, 1000, EasingTypes.OutQuint);
             FadeOut(500);
 
             Expire();
@@ -74,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         /// </summary>
         public void VisualiseSecondHit()
         {
-            ResizeTo(new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER, 1), 50);
+            ResizeTo(new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER), 50);
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 8f2d89dd05..c7bd4a6704 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -90,11 +90,6 @@ namespace osu.Game.Rulesets.Taiko.UI
                             Margin = new MarginPadding { Left = left_area_size },
                             Children = new Drawable[]
                             {
-                                hitExplosionContainer = new Container<HitExplosion>
-                                {
-                                    RelativeSizeAxes = Axes.Y,
-                                    BlendingMode = BlendingMode.Additive
-                                },
                                 new Container
                                 {
                                     Name = "Masked elements",
@@ -103,6 +98,11 @@ namespace osu.Game.Rulesets.Taiko.UI
                                     Masking = true,
                                     Children = new Drawable[]
                                     {
+                                        hitExplosionContainer = new Container<HitExplosion>
+                                        {
+                                            RelativeSizeAxes = Axes.Y,
+                                            BlendingMode = BlendingMode.Additive,
+                                        },
                                         barLineContainer = new Container<DrawableBarLine>
                                         {
                                             RelativeSizeAxes = Axes.Both,

From 7c6540b008b568b5647cf330194342c20f3ee7c1 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 03:49:06 -0300
Subject: [PATCH 157/179] Cleanup status transition code

---
 osu.Game/Users/UserPanel.cs | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index df37b339c3..7a6fdda825 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -176,13 +176,23 @@ namespace osu.Game.Users
 
         private void displayStatus(UserStatus status)
         {
-            statusBar.ResizeHeightTo(status == null ? 0f : status_height, 500, EasingTypes.OutQuint);
-            statusBar.FadeTo(status == null ? 0f : 1f, 500, EasingTypes.OutQuint);
-            ResizeHeightTo(status == null ? height - status_height : height, 500, EasingTypes.OutQuint);
-            if (status == null) return;
+            float transition_duration = 500;
 
-            statusBg.FadeColour(status.GetAppropriateColour(colours), 500, EasingTypes.OutQuint);
-            statusMessage.Text = status.Message;
+            if (status == null)
+            {
+                statusBar.ResizeHeightTo(0f, transition_duration, EasingTypes.OutQuint);
+                statusBar.FadeOut(transition_duration, EasingTypes.OutQuint);
+                ResizeHeightTo(height - status_height, transition_duration, EasingTypes.OutQuint);
+            }
+            else
+            {
+                statusBar.ResizeHeightTo(status_height, transition_duration, EasingTypes.OutQuint);
+                statusBar.FadeIn(transition_duration, EasingTypes.OutQuint);
+                ResizeHeightTo(height, transition_duration, EasingTypes.OutQuint);
+
+                statusBg.FadeColour(status.GetAppropriateColour(colours), 500, EasingTypes.OutQuint);
+                statusMessage.Text = status.Message;
+            }
         }
 
         private class CoverBackgroundSprite : Sprite

From a7914dc1e837f4e47c7f07766067e64e24321845 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 04:00:39 -0300
Subject: [PATCH 158/179] Convert transition_duration to const

---
 osu.Game/Users/UserPanel.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 7a6fdda825..f7714fd819 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -176,7 +176,7 @@ namespace osu.Game.Users
 
         private void displayStatus(UserStatus status)
         {
-            float transition_duration = 500;
+            const float transition_duration = 500;
 
             if (status == null)
             {

From ca6a9b1b71432f3ed68f9cdf3185687b69d6fd28 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 04:21:34 -0300
Subject: [PATCH 159/179] Inline cover

---
 osu.Game/Users/UserPanel.cs | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index f7714fd819..c78a69dac8 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -41,13 +41,15 @@ namespace osu.Game.Users
                 Radius = 4,
             };
 
-            Container cover;
             Children = new Drawable[]
             {
-                cover = new Container
+                new AsyncLoadWrapper(new CoverBackgroundSprite(user)
                 {
-                    RelativeSizeAxes = Axes.Both,
-                },
+                    Anchor = Anchor.Centre,
+                    Origin = Anchor.Centre,
+                    FillMode = FillMode.Fill,
+                    OnLoadComplete = d => d.FadeInFromZero(200),
+                }) { RelativeSizeAxes = Axes.Both },
                 new Box
                 {
                     RelativeSizeAxes = Axes.Both,
@@ -157,14 +159,6 @@ namespace osu.Game.Users
                 },
             };
 
-            cover.Add(new AsyncLoadWrapper(new CoverBackgroundSprite(user)
-            {
-                Anchor = Anchor.Centre,
-                Origin = Anchor.Centre,
-                FillMode = FillMode.Fill,
-                OnLoadComplete = d => d.FadeInFromZero(200),
-            }) { RelativeSizeAxes = Axes.Both });
-
             Status.ValueChanged += displayStatus;
         }
 

From 391134b1d3f328640fcb89f3b5d2228b4fc4750b Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 17:15:51 +0900
Subject: [PATCH 160/179] Adjust glow a bit

---
 .../Objects/Drawables/DrawableCentreHit.cs         |  1 -
 .../Objects/Drawables/DrawableCentreHitStrong.cs   |  1 -
 .../Objects/Drawables/DrawableRimHit.cs            |  1 -
 .../Objects/Drawables/DrawableRimHitStrong.cs      |  1 -
 .../Objects/Drawables/Pieces/CirclePiece.cs        | 14 ++++++++------
 .../Objects/Drawables/Pieces/TaikoPiece.cs         |  5 -----
 6 files changed, 8 insertions(+), 15 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs
index b2760e6914..8bb78669ca 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs
@@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         private void load(OsuColour colours)
         {
             MainPiece.AccentColour = colours.PinkDarker;
-            MainPiece.KiaiFlashColour = colours.PinkLight;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs
index da2e4a5733..434fb9377f 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs
@@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         private void load(OsuColour colours)
         {
             MainPiece.AccentColour = colours.PinkDarker;
-            MainPiece.KiaiFlashColour = colours.PinkLight;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs
index cd5ceaa965..20e8d36105 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs
@@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         private void load(OsuColour colours)
         {
             MainPiece.AccentColour = colours.BlueDarker;
-            MainPiece.KiaiFlashColour = colours.BlueLight;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs
index c9387f6e72..4b1bb62bab 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs
@@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         private void load(OsuColour colours)
         {
             MainPiece.AccentColour = colours.BlueDarker;
-            MainPiece.KiaiFlashColour = colours.BlueLight;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index 992c10fa99..3ea05b6558 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
         public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f;
         public const float SYMBOL_BORDER = 8;
         public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER;
-        private const double pre_beat_transition_time = 50;
+        private const double pre_beat_transition_time = 80;
 
         /// <summary>
         /// The colour of the inner circle and outer glows.
@@ -144,13 +144,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
             Content.Width = 1 / Content.Scale.X;
         }
 
+        private const float edge_alpha_kiai = 0.5f;
+
         private void resetEdgeEffects()
         {
             background.EdgeEffect = new EdgeEffect
             {
                 Type = EdgeEffectType.Glow,
-                Colour = AccentColour,
-                Radius = KiaiMode ? 40 : 8
+                Colour = AccentColour.Opacity(KiaiMode ? edge_alpha_kiai : 1f),
+                Radius = KiaiMode ? 32 : 8
             };
         }
 
@@ -162,11 +164,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
             if (beatIndex % (int)timingPoint.TimeSignature != 0)
                 return;
 
-            double duration = timingPoint.BeatLength * (int)timingPoint.TimeSignature;
+            double duration = timingPoint.BeatLength * 2;
 
-            background.FadeEdgeEffectTo(KiaiFlashColour, pre_beat_transition_time, EasingTypes.OutQuint);
+            background.FadeEdgeEffectTo(1, pre_beat_transition_time, EasingTypes.OutQuint);
             using (background.BeginDelayedSequence(pre_beat_transition_time))
-                background.FadeEdgeEffectTo(AccentColour, duration, EasingTypes.OutQuint);
+                background.FadeEdgeEffectTo(edge_alpha_kiai, duration, EasingTypes.OutQuint);
         }
     }
 }
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
index 9ef9224942..5e7e9e6350 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
@@ -20,11 +20,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
             set { accentColour = value; }
         }
 
-        /// <summary>
-        /// The colour to be flashed on a kiai beat.
-        /// </summary>
-        public Color4 KiaiFlashColour;
-
         private bool kiaiMode;
         /// <summary>
         /// Whether Kiai mode effects are enabled for this circle piece.

From 0dbb2220e090f5c59723f25c275c23939a047ba6 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 21:07:12 +0900
Subject: [PATCH 161/179] Add missing early activation to menu flashes

---
 osu.Game/Screens/Menu/MenuSideFlashes.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index 0cf1fa54fa..77239726e8 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -36,6 +36,8 @@ namespace osu.Game.Screens.Menu
 
         public MenuSideFlashes()
         {
+            EarlyActivationMilliseconds = box_fade_in_time;
+
             RelativeSizeAxes = Axes.Both;
             Anchor = Anchor.Centre;
             Origin = Anchor.Centre;

From 5cb6963940662f61982dbc43457303b70471b45e Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 24 May 2017 22:08:14 +0900
Subject: [PATCH 162/179] Make spinners easier for now

The underlying spin counting doesn't match stabnle, so they have been near impossible to complete until now.
---
 osu.Game.Rulesets.Osu/Objects/Spinner.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index eff60ba935..6ba499739a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -24,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects
             base.ApplyDefaults(controlPointInfo, difficulty);
 
             SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
+
+            // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being.
+            SpinsRequired = (int)(SpinsRequired * 0.6);
         }
     }
 }

From b803e40a7d68d3e996f9d7d2950d6c49fe6eb6df Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 17:02:28 -0300
Subject: [PATCH 163/179] Unbind from room values when disposing

---
 osu.Game/Screens/Multiplayer/DrawableRoom.cs | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index e4e781b839..7365963085 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -245,5 +245,15 @@ namespace osu.Game.Screens.Multiplayer
                 beatmapArtist.Text = string.Empty;
             }
         }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            Room.Name.ValueChanged -= displayName;
+            Room.Host.ValueChanged -= displayUser;
+            Room.Status.ValueChanged -= displayStatus;
+            Room.Beatmap.ValueChanged -= displayBeatmap;
+
+            base.Dispose(isDisposing);
+        }
     }
 }

From b57a3f20562530877fe4520db08f6a1dee3dd4cd Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 19:44:48 -0300
Subject: [PATCH 164/179] Initial layout of user panel and user dropdown

---
 .../Graphics/UserInterface/OsuDropdown.cs     |  10 +-
 .../Sections/General/LoginSettings.cs         | 163 ++++++++++++++++--
 2 files changed, 158 insertions(+), 15 deletions(-)

diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index 9c1799c04c..9d11b8074d 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface
 
         protected override DropdownMenuItem<T> CreateMenuItem(string text, T value) => new OsuDropdownMenuItem(text, value) { AccentColour = AccentColour };
 
-        private class OsuDropdownMenuItem : DropdownMenuItem<T>
+        public class OsuDropdownMenuItem : DropdownMenuItem<T>
         {
             public OsuDropdownMenuItem(string text, T current) : base(text, current)
             {
@@ -115,11 +115,11 @@ namespace osu.Game.Graphics.UserInterface
 
         public class OsuDropdownHeader : DropdownHeader
         {
-            private readonly SpriteText label;
+            protected readonly SpriteText Text;
             protected override string Label
             {
-                get { return label.Text; }
-                set { label.Text = value; }
+                get { return Text.Text; }
+                set { Text.Text = value; }
             }
 
             protected readonly TextAwesome Icon;
@@ -146,7 +146,7 @@ namespace osu.Game.Graphics.UserInterface
 
                 Foreground.Children = new Drawable[]
                 {
-                    label = new OsuSpriteText
+                    Text = new OsuSpriteText
                     {
                         Anchor = Anchor.CentreLeft,
                         Origin = Anchor.CentreLeft,
diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index d94388ed87..da07f55be0 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -13,16 +13,20 @@ using osu.Game.Online.API;
 using OpenTK;
 using osu.Framework.Input;
 using osu.Game.Users;
+using System.ComponentModel;
+using osu.Game.Graphics;
+using OpenTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
+
+using Container = osu.Framework.Graphics.Containers.Container;
 
 namespace osu.Game.Overlays.Settings.Sections.General
 {
-    public class LoginSettings : SettingsSubsection, IOnlineComponent
+    public class LoginSettings : FillFlowContainer, IOnlineComponent
     {
         private bool bounding = true;
         private LoginForm form;
 
-        protected override string Header => "Account";
-
         public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
 
         public bool Bounding
@@ -35,6 +39,14 @@ namespace osu.Game.Overlays.Settings.Sections.General
             }
         }
 
+        public LoginSettings()
+        {
+            RelativeSizeAxes = Axes.X;
+            AutoSizeAxes = Axes.Y;
+            Direction = FillDirection.Vertical;
+            Spacing = new Vector2(0f, 5f);
+        }
+
         [BackgroundDependencyLoader(permitNulls: true)]
         private void load(APIAccess api)
         {
@@ -50,6 +62,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
                 case APIState.Offline:
                     Children = new Drawable[]
                     {
+                        new OsuSpriteText
+                        {
+                            Text = "LOG IN",
+                            Margin = new MarginPadding { Bottom = 10 },
+                            Font = @"Exo2.0-Black",
+                        },
                         form = new LoginForm()
                     };
                     break;
@@ -67,23 +85,52 @@ namespace osu.Game.Overlays.Settings.Sections.General
                     {
                         new OsuSpriteText
                         {
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
                             Text = "Connecting...",
+                            Margin = new MarginPadding { Top = 10, Bottom = 10 },
                         },
                     };
                     break;
                 case APIState.Online:
                     Children = new Drawable[]
                     {
-                        new UserPanel(api.LocalUser.Value)
+                        new FillFlowContainer
                         {
                             RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Padding = new MarginPadding { Left = 20, Right = 20 },
+                            Direction = FillDirection.Vertical,
+                            Spacing = new Vector2(0f, 10f),
+                            Children = new Drawable[]
+                            {
+                                new Container
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Children = new[]
+                                    {
+                                        new OsuSpriteText
+                                        {
+                                            Anchor = Anchor.Centre,
+                                            Origin = Anchor.Centre,
+                                            Text = "Signed in",
+                                            TextSize = 18,
+                                            Font = @"Exo2.0-Bold",
+                                            Margin = new MarginPadding { Top = 5, Bottom = 5 },
+                                        },
+                                    },
+                                },
+                                new UserPanel(api.LocalUser.Value)
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                },
+                                new UserDropdown
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                },
+                            },
                         },
-                        new OsuButton
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            Text = "Sign out",
-                            Action = api.Logout
-                        }
                     };
                     break;
             }
@@ -171,5 +218,101 @@ namespace osu.Game.Overlays.Settings.Sections.General
                 return base.OnFocus(state);
             }
         }
+
+        private class UserDropdown : OsuEnumDropdown<UserAction>
+        {
+            protected override DropdownHeader CreateHeader() => new UserDropdownHeader { AccentColour = AccentColour };
+            protected override Menu CreateMenu() => new UserDropdownMenu();
+            protected override DropdownMenuItem<UserAction> CreateMenuItem(string text, UserAction value) => new UserDropdownMenuItem(text, value) { AccentColour = AccentColour };
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                AccentColour = colours.Gray5;
+            }
+
+            private class UserDropdownHeader : OsuDropdownHeader
+            {
+                protected readonly TextAwesome statusIcon;
+
+                public UserDropdownHeader()
+                {
+                    Foreground.Padding = new MarginPadding { Left = 10, Right = 10 };
+                    Margin = new MarginPadding { Bottom = 5 };
+                    Masking = true;
+                    CornerRadius = 5;
+                    EdgeEffect = new EdgeEffect
+                    {
+                        Type = EdgeEffectType.Shadow,
+                        Colour = Color4.Black.Opacity(0.25f),
+                        Radius = 4,
+                    };
+
+                    Icon.TextSize = 14;
+                    Icon.Margin = new MarginPadding(0);
+
+                    Foreground.Add(statusIcon = new TextAwesome
+                    {
+                        Anchor = Anchor.CentreLeft,
+                        Origin = Anchor.CentreLeft,
+                        Icon = FontAwesome.fa_circle_o,
+                        TextSize = 14,
+                    });
+
+                    //todo: Magic number
+                    Text.Margin = new MarginPadding { Left = 20 };
+                }
+
+                [BackgroundDependencyLoader]
+                private void load(OsuColour colours)
+                {
+                    BackgroundColour = colours.Gray3;
+                }
+
+                public void SetStatusColour(Color4 colour) => statusIcon.FadeColour(colour, 500, EasingTypes.OutQuint);
+            }
+
+            private class UserDropdownMenu : OsuMenu
+            {
+                public UserDropdownMenu()
+                {
+                    CornerRadius = 5;
+                    ItemsContainer.Padding = new MarginPadding(0);
+                    Masking = true;
+                    EdgeEffect = new EdgeEffect
+                    {
+                        Type = EdgeEffectType.Shadow,
+                        Colour = Color4.Black.Opacity(0.25f),
+                        Radius = 4,
+                    };
+                }
+
+                [BackgroundDependencyLoader]
+                private void load(OsuColour colours)
+                {
+                    Background.Colour = colours.Gray3;
+                }
+            }
+
+            private class UserDropdownMenuItem : OsuDropdownMenuItem
+            {
+                public UserDropdownMenuItem(string text, UserAction current) : base(text, current)
+                {
+                    Foreground.Padding = new MarginPadding(5);
+                    CornerRadius = 5;
+                }
+            }
+        }
+
+        private enum UserAction
+        {
+            Online,
+            [Description(@"Do not disturb")]
+            DoNotDisturb,
+            [Description(@"Appear offline")]
+            AppearOffline,
+            [Description(@"Sign out")]
+            SignOut,
+        }
     }
 }

From efd4c574312824258a973622ec6f2105e937f952 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 21:09:18 -0300
Subject: [PATCH 165/179] Dropdown actions, +User.Status,
 +UserStatusDoNotDisturb, properly align UserDropdownMenuItem

---
 .../Sections/General/LoginSettings.cs         | 54 ++++++++++++++++---
 osu.Game/Users/User.cs                        |  3 ++
 osu.Game/Users/UserStatus.cs                  |  6 +++
 3 files changed, 57 insertions(+), 6 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index da07f55be0..b740aa282a 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -26,6 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
     {
         private bool bounding = true;
         private LoginForm form;
+        private OsuColour colours;
 
         public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
 
@@ -48,8 +49,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
         }
 
         [BackgroundDependencyLoader(permitNulls: true)]
-        private void load(APIAccess api)
+        private void load(OsuColour colours, APIAccess api)
         {
+            this.colours = colours;
             api?.Register(this);
         }
 
@@ -93,6 +95,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
                     };
                     break;
                 case APIState.Online:
+                    UserDropdown dropdown;
+                    UserPanel panel;
                     Children = new Drawable[]
                     {
                         new FillFlowContainer
@@ -121,17 +125,40 @@ namespace osu.Game.Overlays.Settings.Sections.General
                                         },
                                     },
                                 },
-                                new UserPanel(api.LocalUser.Value)
+                                panel = new UserPanel(api.LocalUser.Value)
                                 {
                                     RelativeSizeAxes = Axes.X,
                                 },
-                                new UserDropdown
+                                dropdown = new UserDropdown
                                 {
                                     RelativeSizeAxes = Axes.X,
                                 },
                             },
                         },
                     };
+                    panel.Status.BindTo(api.LocalUser.Value.Status);
+                    dropdown.Current.ValueChanged += newValue =>
+                    {
+                        switch (newValue)
+                        {
+                            case UserAction.Online:
+                                api.LocalUser.Value.Status.Value = new UserStatusOnline();
+                                dropdown.StatusColour = colours.Green;
+                                break;
+                            case UserAction.DoNotDisturb:
+                                api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
+                                dropdown.StatusColour = colours.Red;
+                                break;
+                            case UserAction.AppearOffline:
+                                api.LocalUser.Value.Status.Value = new UserStatusOffline();
+                                dropdown.StatusColour = colours.Gray7;
+                                break;
+                            case UserAction.SignOut:
+                                api.Logout();
+                                break;
+                        }
+                    };
+                    dropdown.Current.TriggerChange();
                     break;
             }
 
@@ -225,6 +252,14 @@ namespace osu.Game.Overlays.Settings.Sections.General
             protected override Menu CreateMenu() => new UserDropdownMenu();
             protected override DropdownMenuItem<UserAction> CreateMenuItem(string text, UserAction value) => new UserDropdownMenuItem(text, value) { AccentColour = AccentColour };
 
+            public Color4 StatusColour
+            {
+                set
+                {
+                    (Header as UserDropdownHeader).StatusColour = value;
+                }
+            }
+
             [BackgroundDependencyLoader]
             private void load(OsuColour colours)
             {
@@ -235,6 +270,14 @@ namespace osu.Game.Overlays.Settings.Sections.General
             {
                 protected readonly TextAwesome statusIcon;
 
+                public Color4 StatusColour
+                {
+                    set
+                    {
+                        statusIcon.FadeColour(value, 500, EasingTypes.OutQuint);
+                    }
+                }
+
                 public UserDropdownHeader()
                 {
                     Foreground.Padding = new MarginPadding { Left = 10, Right = 10 };
@@ -268,8 +311,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
                 {
                     BackgroundColour = colours.Gray3;
                 }
-
-                public void SetStatusColour(Color4 colour) => statusIcon.FadeColour(colour, 500, EasingTypes.OutQuint);
             }
 
             private class UserDropdownMenu : OsuMenu
@@ -298,7 +339,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
             {
                 public UserDropdownMenuItem(string text, UserAction current) : base(text, current)
                 {
-                    Foreground.Padding = new MarginPadding(5);
+                    //todo: Another magic number
+                    Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 19, Right = 5 };
                     CornerRadius = 5;
                 }
             }
diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs
index 1361eefcff..93933c8fe9 100644
--- a/osu.Game/Users/User.cs
+++ b/osu.Game/Users/User.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using Newtonsoft.Json;
+using osu.Framework.Configuration;
 
 namespace osu.Game.Users
 {
@@ -19,6 +20,8 @@ namespace osu.Game.Users
         [JsonProperty(@"country")]
         public Country Country;
 
+        public Bindable<UserStatus> Status = new Bindable<UserStatus>();
+
         //public Team Team;
 
         [JsonProperty(@"profile_colour")]
diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs
index dcb5ccbd8f..15e6c4fb60 100644
--- a/osu.Game/Users/UserStatus.cs
+++ b/osu.Game/Users/UserStatus.cs
@@ -58,4 +58,10 @@ namespace osu.Game.Users
         public override string Message => @"Modding a map";
         public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
     }
+    
+    public class UserStatusDoNotDisturb : UserStatus
+    {
+        public override string Message => @"Do not disturb";
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark;
+    }
 }

From 9b863f60ab5e91dd366d64f4272eaeac7b29ec81 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 21:32:46 -0300
Subject: [PATCH 166/179] Adjust dropdown layout

---
 osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index b740aa282a..435c6cf9bb 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -317,6 +317,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
             {
                 public UserDropdownMenu()
                 {
+                    Margin = new MarginPadding { Bottom = 5 };
                     CornerRadius = 5;
                     ItemsContainer.Padding = new MarginPadding(0);
                     Masking = true;

From 8e09b738b0177e98a56bcdfdd373601a7c742682 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 21:51:00 -0300
Subject: [PATCH 167/179] Remove magic numbers

---
 osu.Game/Graphics/UserInterface/OsuDropdown.cs        |  6 +++---
 .../Settings/Sections/General/LoginSettings.cs        | 11 ++++++-----
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index 9d11b8074d..14483f3bfb 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Graphics.UserInterface
                     AutoSizeAxes = Axes.Y,
                     Children = new Drawable[]
                     {
-                        chevron = new TextAwesome
+                        Chevron = new TextAwesome
                         {
                             AlwaysPresent = true,
                             Icon = FontAwesome.fa_chevron_right,
@@ -84,12 +84,12 @@ namespace osu.Game.Graphics.UserInterface
 
             private Color4? accentColour;
 
-            private readonly TextAwesome chevron;
+            protected readonly TextAwesome Chevron;
 
             protected override void FormatForeground(bool hover = false)
             {
                 base.FormatForeground(hover);
-                chevron.Alpha = hover ? 1 : 0;
+                Chevron.Alpha = hover ? 1 : 0;
             }
 
             public Color4 AccentColour
diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 435c6cf9bb..a4b54a52a2 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -268,6 +268,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
 
             private class UserDropdownHeader : OsuDropdownHeader
             {
+                public static readonly float LABEL_LEFT_MARGIN = 20;
+
                 protected readonly TextAwesome statusIcon;
 
                 public Color4 StatusColour
@@ -301,9 +303,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
                         Icon = FontAwesome.fa_circle_o,
                         TextSize = 14,
                     });
-
-                    //todo: Magic number
-                    Text.Margin = new MarginPadding { Left = 20 };
+                    
+                    Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN };
                 }
 
                 [BackgroundDependencyLoader]
@@ -340,8 +341,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
             {
                 public UserDropdownMenuItem(string text, UserAction current) : base(text, current)
                 {
-                    //todo: Another magic number
-                    Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 19, Right = 5 };
+                    Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = UserDropdownHeader.LABEL_LEFT_MARGIN, Right = 5 };
+                    Chevron.Margin = new MarginPadding { Left = 2, Right = 3 };
                     CornerRadius = 5;
                 }
             }

From 9cad34440183384270888a2767289d5d9c820a90 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 22:02:32 -0300
Subject: [PATCH 168/179] Revert back header for login form, fix incorrect
 spacing on header

---
 osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index a4b54a52a2..51b95a4d8e 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -66,8 +66,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
                     {
                         new OsuSpriteText
                         {
-                            Text = "LOG IN",
-                            Margin = new MarginPadding { Bottom = 10 },
+                            Text = "ACCOUNT",
+                            Margin = new MarginPadding { Bottom = 5 },
                             Font = @"Exo2.0-Black",
                         },
                         form = new LoginForm()

From ec3c92fc3c31bfd4c9764cbf05621e0fbdb1a702 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 22:26:48 -0300
Subject: [PATCH 169/179] Trim whitespace

---
 osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs | 2 +-
 osu.Game/Users/UserStatus.cs                                 | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 51b95a4d8e..c2a4e4c833 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -303,7 +303,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
                         Icon = FontAwesome.fa_circle_o,
                         TextSize = 14,
                     });
-                    
+
                     Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN };
                 }
 
diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs
index 15e6c4fb60..461008db0f 100644
--- a/osu.Game/Users/UserStatus.cs
+++ b/osu.Game/Users/UserStatus.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Users
         public override string Message => @"Modding a map";
         public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
     }
-    
+
     public class UserStatusDoNotDisturb : UserStatus
     {
         public override string Message => @"Do not disturb";

From e22eb1f20578ababb88cd4e834ab434167248fd2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Wed, 24 May 2017 22:39:07 -0300
Subject: [PATCH 170/179] CI fixes

---
 .../Overlays/Settings/Sections/General/LoginSettings.cs  | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index c2a4e4c833..501618be7f 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -256,7 +256,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
             {
                 set
                 {
-                    (Header as UserDropdownHeader).StatusColour = value;
+                    var h = Header as UserDropdownHeader;
+                    if (h == null) return;
+                    h.StatusColour = value;
                 }
             }
 
@@ -268,10 +270,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
 
             private class UserDropdownHeader : OsuDropdownHeader
             {
-                public static readonly float LABEL_LEFT_MARGIN = 20;
-
-                protected readonly TextAwesome statusIcon;
+                public const float LABEL_LEFT_MARGIN = 20;
 
+                private readonly TextAwesome statusIcon;
                 public Color4 StatusColour
                 {
                     set

From be81346573dc4e3f6b94258c4442a42d16129458 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 25 May 2017 14:47:25 +0900
Subject: [PATCH 171/179] Attempt to fix inner scope warning

---
 .../Sections/General/LoginSettings.cs         | 62 +++++++++----------
 1 file changed, 29 insertions(+), 33 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 501618be7f..2c92e8653e 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -95,8 +95,33 @@ namespace osu.Game.Overlays.Settings.Sections.General
                     };
                     break;
                 case APIState.Online:
-                    UserDropdown dropdown;
-                    UserPanel panel;
+                    UserDropdown dropdown = new UserDropdown { RelativeSizeAxes = Axes.X };
+                    dropdown.Current.ValueChanged += newValue =>
+                    {
+                        switch (newValue)
+                        {
+                            case UserAction.Online:
+                                api.LocalUser.Value.Status.Value = new UserStatusOnline();
+                                dropdown.StatusColour = colours.Green;
+                                break;
+                            case UserAction.DoNotDisturb:
+                                api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
+                                dropdown.StatusColour = colours.Red;
+                                break;
+                            case UserAction.AppearOffline:
+                                api.LocalUser.Value.Status.Value = new UserStatusOffline();
+                                dropdown.StatusColour = colours.Gray7;
+                                break;
+                            case UserAction.SignOut:
+                                api.Logout();
+                                break;
+                        }
+                    };
+                    dropdown.Current.TriggerChange();
+
+                    UserPanel panel = new UserPanel(api.LocalUser.Value) { RelativeSizeAxes = Axes.X };
+                    panel.Status.BindTo(api.LocalUser.Value.Status);
+
                     Children = new Drawable[]
                     {
                         new FillFlowContainer
@@ -125,40 +150,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
                                         },
                                     },
                                 },
-                                panel = new UserPanel(api.LocalUser.Value)
-                                {
-                                    RelativeSizeAxes = Axes.X,
-                                },
-                                dropdown = new UserDropdown
-                                {
-                                    RelativeSizeAxes = Axes.X,
-                                },
+                                panel,
+                                dropdown,
                             },
                         },
                     };
-                    panel.Status.BindTo(api.LocalUser.Value.Status);
-                    dropdown.Current.ValueChanged += newValue =>
-                    {
-                        switch (newValue)
-                        {
-                            case UserAction.Online:
-                                api.LocalUser.Value.Status.Value = new UserStatusOnline();
-                                dropdown.StatusColour = colours.Green;
-                                break;
-                            case UserAction.DoNotDisturb:
-                                api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
-                                dropdown.StatusColour = colours.Red;
-                                break;
-                            case UserAction.AppearOffline:
-                                api.LocalUser.Value.Status.Value = new UserStatusOffline();
-                                dropdown.StatusColour = colours.Gray7;
-                                break;
-                            case UserAction.SignOut:
-                                api.Logout();
-                                break;
-                        }
-                    };
-                    dropdown.Current.TriggerChange();
                     break;
             }
 

From d7fa6933be18d192bbaf097dfb032606ec8f719c Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 25 May 2017 16:31:56 +0900
Subject: [PATCH 172/179] Fix potential nullref

---
 osu.Game/Users/UserPanel.cs | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index c78a69dac8..bdfe6d1c8e 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -158,14 +158,19 @@ namespace osu.Game.Users
                     },
                 },
             };
-
-            Status.ValueChanged += displayStatus;
         }
 
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
             this.colours = colours;
+            Status.ValueChanged += displayStatus;
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+            Status.TriggerChange();
         }
 
         private void displayStatus(UserStatus status)

From 1a255fdf4818358f2478e674601a7fe6399ddce6 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 25 May 2017 19:47:18 +0900
Subject: [PATCH 173/179] Fix display order regression

---
 .../Sections/General/LoginSettings.cs         | 59 ++++++++++---------
 1 file changed, 30 insertions(+), 29 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 2c92e8653e..d8db44607a 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -28,6 +28,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
         private LoginForm form;
         private OsuColour colours;
 
+        private UserPanel panel;
+        private UserDropdown dropdown;
+
         public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
 
         public bool Bounding
@@ -95,33 +98,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
                     };
                     break;
                 case APIState.Online:
-                    UserDropdown dropdown = new UserDropdown { RelativeSizeAxes = Axes.X };
-                    dropdown.Current.ValueChanged += newValue =>
-                    {
-                        switch (newValue)
-                        {
-                            case UserAction.Online:
-                                api.LocalUser.Value.Status.Value = new UserStatusOnline();
-                                dropdown.StatusColour = colours.Green;
-                                break;
-                            case UserAction.DoNotDisturb:
-                                api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
-                                dropdown.StatusColour = colours.Red;
-                                break;
-                            case UserAction.AppearOffline:
-                                api.LocalUser.Value.Status.Value = new UserStatusOffline();
-                                dropdown.StatusColour = colours.Gray7;
-                                break;
-                            case UserAction.SignOut:
-                                api.Logout();
-                                break;
-                        }
-                    };
-                    dropdown.Current.TriggerChange();
-
-                    UserPanel panel = new UserPanel(api.LocalUser.Value) { RelativeSizeAxes = Axes.X };
-                    panel.Status.BindTo(api.LocalUser.Value.Status);
-
                     Children = new Drawable[]
                     {
                         new FillFlowContainer
@@ -150,11 +126,36 @@ namespace osu.Game.Overlays.Settings.Sections.General
                                         },
                                     },
                                 },
-                                panel,
-                                dropdown,
+                                panel = new UserPanel(api.LocalUser.Value) { RelativeSizeAxes = Axes.X },
+                                dropdown = new UserDropdown { RelativeSizeAxes = Axes.X },
                             },
                         },
                     };
+
+                    panel.Status.BindTo(api.LocalUser.Value.Status);
+
+                    dropdown.Current.TriggerChange();
+                    dropdown.Current.ValueChanged += newValue =>
+                    {
+                        switch (newValue)
+                        {
+                            case UserAction.Online:
+                                api.LocalUser.Value.Status.Value = new UserStatusOnline();
+                                dropdown.StatusColour = colours.Green;
+                                break;
+                            case UserAction.DoNotDisturb:
+                                api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
+                                dropdown.StatusColour = colours.Red;
+                                break;
+                            case UserAction.AppearOffline:
+                                api.LocalUser.Value.Status.Value = new UserStatusOffline();
+                                dropdown.StatusColour = colours.Gray7;
+                                break;
+                            case UserAction.SignOut:
+                                api.Logout();
+                                break;
+                        }
+                    };
                     break;
             }
 

From 97e57178a77af3fee0c8cd448df623f042115846 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 25 May 2017 21:33:04 +0900
Subject: [PATCH 174/179] Make triangles use raw vertices instead of sprites.

---
 osu-framework                              |   2 +-
 osu.Game/Graphics/Backgrounds/Triangles.cs | 211 ++++++++++++++++-----
 2 files changed, 166 insertions(+), 47 deletions(-)

diff --git a/osu-framework b/osu-framework
index 777996fb97..3e989ae630 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 777996fb9731ba1895a5ab1323cbbc97259ff741
+Subproject commit 3e989ae63031a75e622225d6f4d14c6e26170b97
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 5cca57be8a..28e57eb470 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -5,16 +5,28 @@ using System.Linq;
 using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
 using osu.Framework.MathUtils;
 using OpenTK;
 using OpenTK.Graphics;
 using System;
+using System.Runtime.InteropServices;
+using osu.Framework.Graphics.OpenGL;
+using osu.Framework.Graphics.Shaders;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.OpenGL.Buffers;
+using OpenTK.Graphics.ES30;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Allocation;
+using System.Collections.Generic;
+using osu.Framework.Graphics.Batches;
 
 namespace osu.Game.Graphics.Backgrounds
 {
-    public class Triangles : Container<Triangle>
+    public class Triangles : Drawable
     {
+        private const float triangle_size = 100;
+
         public override bool HandleInput => false;
 
         public Color4 ColourLight = Color4.White;
@@ -49,6 +61,28 @@ namespace osu.Game.Graphics.Backgrounds
         /// </summary>
         public float Velocity = 1;
 
+        private readonly List<TriangleParticle> parts = new List<TriangleParticle>();
+
+        private Shader shader;
+        private readonly Texture texture;
+
+        public Triangles()
+        {
+            texture = Texture.WhitePixel;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(ShaderManager shaders)
+        {
+            shader = shaders?.Load("Triangles", FragmentShaderDescriptor.TEXTURE_ROUNDED);
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+            addTriangles(true);
+        }
+
         public float TriangleScale
         {
             get { return triangleScale; }
@@ -57,42 +91,69 @@ namespace osu.Game.Graphics.Backgrounds
                 float change = value / triangleScale;
                 triangleScale = value;
 
-                if (change != 1)
-                    Children.ForEach(t => t.Scale *= change);
+                for (int i = 0; i < parts.Count; i++)
+                {
+                    TriangleParticle newParticle = parts[i];
+                    newParticle.Scale *= change;
+                    parts[i] = newParticle;
+                }
             }
         }
 
-        protected override void LoadComplete()
-        {
-            base.LoadComplete();
-
-            addTriangles(true);
-        }
-
-        private int aimTriangleCount => (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio);
-
         protected override void Update()
         {
             base.Update();
 
-            float adjustedAlpha = HideAlphaDiscrepancies ?
-                // Cubically scale alpha to make it drop off more sharply.
-                (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
-                1;
+            Invalidate(Invalidation.DrawNode, shallPropagate: false);
 
-            foreach (var t in Children)
+            for (int i = 0; i < parts.Count; i++)
             {
-                t.Alpha = adjustedAlpha;
-                t.Position -= new Vector2(0, (float)(t.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale * Velocity);
-                if (ExpireOffScreenTriangles && t.DrawPosition.Y + t.DrawSize.Y * t.Scale.Y < 0)
-                    t.Expire();
-            }
+                TriangleParticle newParticle = parts[i];
 
-            if (CreateNewTriangles)
-                addTriangles(false);
+                newParticle.Position += new Vector2(0, -(float)(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
+
+                float adjustedAlpha = HideAlphaDiscrepancies ?
+                    // Cubically scale alpha to make it drop off more sharply.
+                    (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
+                    1;
+
+                newParticle.Colour.A = adjustedAlpha;
+
+                parts[i] = newParticle;
+
+                if (!CreateNewTriangles)
+                    continue;
+
+                float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight;
+
+                if (bottomPos < 0)
+                    parts[i] = createTriangle(false);
+            }
         }
 
-        protected virtual Triangle CreateTriangle()
+        private void addTriangles(bool randomY)
+        {
+            int aimTriangleCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio);
+
+            for (int i = 0; i < aimTriangleCount; i++)
+                parts.Add(createTriangle(randomY));
+        }
+
+        private TriangleParticle createTriangle(bool randomY)
+        {
+            var particle = CreateTriangle();
+
+            particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1);
+            particle.Colour = CreateTriangleShade();
+
+            return particle;
+        }
+
+        /// <summary>
+        /// Creates a triangle particle with a random scale.
+        /// </summary>
+        /// <returns>The triangle particle.</returns>
+        protected virtual TriangleParticle CreateTriangle()
         {
             const float std_dev = 0.16f;
             const float mean = 0.5f;
@@ -102,32 +163,90 @@ namespace osu.Game.Graphics.Backgrounds
             float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); //random normal(0,1)
             var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); //random normal(mean,stdDev^2)
 
-            const float size = 100;
-
-            return new EquilateralTriangle
-            {
-                Origin = Anchor.TopCentre,
-                RelativePositionAxes = Axes.Both,
-                Size = new Vector2(size),
-                Scale = new Vector2(scale),
-                EdgeSmoothness = new Vector2(1),
-                Colour = GetTriangleShade(),
-                Depth = scale,
-            };
+            return new TriangleParticle { Scale = scale };
         }
 
-        protected virtual Color4 GetTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1);
+        /// <summary>
+        /// Creates a shade of colour for the triangles.
+        /// </summary>
+        /// <returns>The colour.</returns>
+        protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1);
 
-        private void addTriangles(bool randomY)
+        protected override DrawNode CreateDrawNode() => new TrianglesDrawNode();
+
+        private TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData();
+        protected override void ApplyDrawNode(DrawNode node)
         {
-            int addCount = aimTriangleCount - Children.Count();
-            for (int i = 0; i < addCount; i++)
+            base.ApplyDrawNode(node);
+
+            var trianglesNode = node as TrianglesDrawNode;
+
+            trianglesNode.Shader = shader;
+            trianglesNode.Texture = texture;
+            trianglesNode.Size = DrawSize;
+            trianglesNode.Shared = sharedData;
+
+            trianglesNode.Parts.Clear();
+            trianglesNode.Parts.AddRange(parts);
+        }
+
+        public class TrianglesDrawNodeSharedData
+        {
+            public LinearBatch<TexturedVertex2D> VertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
+        }
+
+        public class TrianglesDrawNode : DrawNode
+        {
+            public Shader Shader;
+            public Texture Texture;
+
+            public float Time;
+            public TrianglesDrawNodeSharedData Shared;
+
+            public readonly List<TriangleParticle> Parts = new List<TriangleParticle>();
+            public Vector2 Size;
+
+            public override void Draw(Action<TexturedVertex2D> vertexAction)
             {
-                var sprite = CreateTriangle();
-                float triangleHeight = sprite.DrawHeight / DrawHeight;
-                sprite.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() * (1 + triangleHeight) - triangleHeight : 1);
-                Add(sprite);
+                base.Draw(vertexAction);
+
+                Shader.Bind();
+                Texture.TextureGL.Bind();
+
+                int updateStart = -1, updateEnd = 0;
+                for (int i = 0; i < Parts.Count; ++i)
+                {
+                    if (updateStart == -1)
+                        updateStart = i;
+                    updateEnd = i + 1;
+
+                    TriangleParticle particle = Parts[i];
+
+                    Vector2 offset = new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f);
+
+                    var triangle = new Triangle(
+                        (particle.Position * Size) * DrawInfo.Matrix,
+                        (particle.Position * Size + offset * triangle_size) * DrawInfo.Matrix,
+                        (particle.Position * Size + new Vector2(-offset.X, offset.Y) * triangle_size) * DrawInfo.Matrix
+                    );
+
+                    int index = i * 6;
+
+                    ColourInfo colourInfo = DrawInfo.Colour;
+                    colourInfo.ApplyChild(particle.Colour);
+
+                    Texture.DrawTriangle(triangle, colourInfo, null, Shared.VertexBatch.Add);
+                }
+
+                Shader.Unbind();
             }
         }
+
+        public struct TriangleParticle
+        {
+            public Vector2 Position;
+            public Color4 Colour;
+            public float Scale;
+        }
     }
 }

From 86f6db2d31da91beb0734e72b46426875ae6dfc0 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 25 May 2017 21:33:49 +0900
Subject: [PATCH 175/179] Cleanup.

---
 osu-framework                              |  2 +-
 osu.Game/Graphics/Backgrounds/Triangles.cs | 27 ++++++----------------
 2 files changed, 8 insertions(+), 21 deletions(-)

diff --git a/osu-framework b/osu-framework
index 3e989ae630..4c84c0ef14 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 3e989ae63031a75e622225d6f4d14c6e26170b97
+Subproject commit 4c84c0ef14ca1fab6098d900c036dff4c85987a5
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 28e57eb470..702dd7585b 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -1,19 +1,15 @@
 // 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.Linq;
-using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.MathUtils;
 using OpenTK;
 using OpenTK.Graphics;
 using System;
-using System.Runtime.InteropServices;
 using osu.Framework.Graphics.OpenGL;
 using osu.Framework.Graphics.Shaders;
 using osu.Framework.Graphics.Textures;
-using osu.Framework.Graphics.OpenGL.Buffers;
 using OpenTK.Graphics.ES30;
 using osu.Framework.Graphics.Colour;
 using osu.Framework.Graphics.Primitives;
@@ -74,7 +70,7 @@ namespace osu.Game.Graphics.Backgrounds
         [BackgroundDependencyLoader]
         private void load(ShaderManager shaders)
         {
-            shader = shaders?.Load("Triangles", FragmentShaderDescriptor.TEXTURE_ROUNDED);
+            shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
         }
 
         protected override void LoadComplete()
@@ -110,7 +106,7 @@ namespace osu.Game.Graphics.Backgrounds
             {
                 TriangleParticle newParticle = parts[i];
 
-                newParticle.Position += new Vector2(0, -(float)(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
+                newParticle.Position += new Vector2(0, -(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
 
                 float adjustedAlpha = HideAlphaDiscrepancies ?
                     // Cubically scale alpha to make it drop off more sharply.
@@ -174,12 +170,12 @@ namespace osu.Game.Graphics.Backgrounds
 
         protected override DrawNode CreateDrawNode() => new TrianglesDrawNode();
 
-        private TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData();
+        private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData();
         protected override void ApplyDrawNode(DrawNode node)
         {
             base.ApplyDrawNode(node);
 
-            var trianglesNode = node as TrianglesDrawNode;
+            var trianglesNode = (TrianglesDrawNode)node;
 
             trianglesNode.Shader = shader;
             trianglesNode.Texture = texture;
@@ -213,25 +209,16 @@ namespace osu.Game.Graphics.Backgrounds
                 Shader.Bind();
                 Texture.TextureGL.Bind();
 
-                int updateStart = -1, updateEnd = 0;
-                for (int i = 0; i < Parts.Count; ++i)
+                foreach (TriangleParticle particle in Parts)
                 {
-                    if (updateStart == -1)
-                        updateStart = i;
-                    updateEnd = i + 1;
-
-                    TriangleParticle particle = Parts[i];
-
-                    Vector2 offset = new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f);
+                    var offset = new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f);
 
                     var triangle = new Triangle(
-                        (particle.Position * Size) * DrawInfo.Matrix,
+                        particle.Position * Size * DrawInfo.Matrix,
                         (particle.Position * Size + offset * triangle_size) * DrawInfo.Matrix,
                         (particle.Position * Size + new Vector2(-offset.X, offset.Y) * triangle_size) * DrawInfo.Matrix
                     );
 
-                    int index = i * 6;
-
                     ColourInfo colourInfo = DrawInfo.Colour;
                     colourInfo.ApplyChild(particle.Colour);
 

From 91dba765c2f572dcb1b5979ef8e506209441aeed Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 25 May 2017 22:03:36 +0900
Subject: [PATCH 176/179] Add back the concept of triangle ordering by size.

---
 osu.Game/Graphics/Backgrounds/Triangles.cs | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 702dd7585b..6cc7857fa9 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -16,6 +16,7 @@ using osu.Framework.Graphics.Primitives;
 using osu.Framework.Allocation;
 using System.Collections.Generic;
 using osu.Framework.Graphics.Batches;
+using osu.Framework.Lists;
 
 namespace osu.Game.Graphics.Backgrounds
 {
@@ -57,7 +58,7 @@ namespace osu.Game.Graphics.Backgrounds
         /// </summary>
         public float Velocity = 1;
 
-        private readonly List<TriangleParticle> parts = new List<TriangleParticle>();
+        private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default);
 
         private Shader shader;
         private readonly Texture texture;
@@ -123,15 +124,17 @@ namespace osu.Game.Graphics.Backgrounds
                 float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight;
 
                 if (bottomPos < 0)
-                    parts[i] = createTriangle(false);
+                    parts.RemoveAt(i);
             }
+
+            addTriangles(false);
         }
 
         private void addTriangles(bool randomY)
         {
             int aimTriangleCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio);
 
-            for (int i = 0; i < aimTriangleCount; i++)
+            for (int i = 0; i < aimTriangleCount - parts.Count; i++)
                 parts.Add(createTriangle(randomY));
         }
 
@@ -229,11 +232,20 @@ namespace osu.Game.Graphics.Backgrounds
             }
         }
 
-        public struct TriangleParticle
+        public struct TriangleParticle : IComparable<TriangleParticle>
         {
             public Vector2 Position;
             public Color4 Colour;
             public float Scale;
+
+            /// <summary>
+            /// Compares two <see cref="TriangleParticle"/>s. This is a reverse comparer because when the
+            /// triangles are added to the particles list, they should be drawn from largest to smallest
+            /// such that the smaller triangles appear on top.
+            /// </summary>
+            /// <param name="other"></param>
+            /// <returns></returns>
+            public int CompareTo(TriangleParticle other) => other.Scale.CompareTo(Scale);
         }
     }
 }

From c8cf387f5ff1d6fc41d02d166ad37759bc5fffca Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 25 May 2017 22:09:02 +0900
Subject: [PATCH 177/179] A bit more cleanup.

---
 osu.Game/Graphics/Backgrounds/Triangles.cs | 26 +++++++++++++++-------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 6cc7857fa9..9a19819af8 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -107,13 +107,13 @@ namespace osu.Game.Graphics.Backgrounds
             {
                 TriangleParticle newParticle = parts[i];
 
-                newParticle.Position += new Vector2(0, -(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
-
                 float adjustedAlpha = HideAlphaDiscrepancies ?
                     // Cubically scale alpha to make it drop off more sharply.
                     (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
                     1;
 
+
+                newParticle.Position += new Vector2(0, -(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
                 newParticle.Colour.A = adjustedAlpha;
 
                 parts[i] = newParticle;
@@ -140,7 +140,7 @@ namespace osu.Game.Graphics.Backgrounds
 
         private TriangleParticle createTriangle(bool randomY)
         {
-            var particle = CreateTriangle();
+            TriangleParticle particle = CreateTriangle();
 
             particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1);
             particle.Colour = CreateTriangleShade();
@@ -189,17 +189,16 @@ namespace osu.Game.Graphics.Backgrounds
             trianglesNode.Parts.AddRange(parts);
         }
 
-        public class TrianglesDrawNodeSharedData
+        private class TrianglesDrawNodeSharedData
         {
-            public LinearBatch<TexturedVertex2D> VertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
+            public readonly LinearBatch<TexturedVertex2D> VertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
         }
 
-        public class TrianglesDrawNode : DrawNode
+        private class TrianglesDrawNode : DrawNode
         {
             public Shader Shader;
             public Texture Texture;
 
-            public float Time;
             public TrianglesDrawNodeSharedData Shared;
 
             public readonly List<TriangleParticle> Parts = new List<TriangleParticle>();
@@ -232,10 +231,21 @@ namespace osu.Game.Graphics.Backgrounds
             }
         }
 
-        public struct TriangleParticle : IComparable<TriangleParticle>
+        protected struct TriangleParticle : IComparable<TriangleParticle>
         {
+            /// <summary>
+            /// The position of the top vertex of the triangle.
+            /// </summary>
             public Vector2 Position;
+
+            /// <summary>
+            /// The colour of the triangle.
+            /// </summary>
             public Color4 Colour;
+
+            /// <summary>
+            /// The scale of the triangle.
+            /// </summary>
             public float Scale;
 
             /// <summary>

From 9eec2edd30a56ed2281687a63980bf0a311b36b5 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 25 May 2017 22:43:33 +0900
Subject: [PATCH 178/179] Fix initial login state not being reflected in user
 panel

---
 osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index d8db44607a..561f81d6c3 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -134,7 +134,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
 
                     panel.Status.BindTo(api.LocalUser.Value.Status);
 
-                    dropdown.Current.TriggerChange();
                     dropdown.Current.ValueChanged += newValue =>
                     {
                         switch (newValue)
@@ -156,6 +155,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
                                 break;
                         }
                     };
+                    dropdown.Current.TriggerChange();
+
                     break;
             }
 

From ec8f49df0fce0b4bdca9c31915a523ffb0f8dc6d Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 25 May 2017 22:46:50 +0900
Subject: [PATCH 179/179] Update framework

---
 osu-framework | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index 4c84c0ef14..8baad1b948 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 4c84c0ef14ca1fab6098d900c036dff4c85987a5
+Subproject commit 8baad1b9484b9f35724e2f965c18cfe710907d80