From 8b25e4c9eea54cb5353da8ca743ba38edff0d60d Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 3 Jan 2019 12:04:36 +0900
Subject: [PATCH 01/43] Fix searching for "channel" matching all channels

---
 osu.Game/Overlays/Chat/Selection/ChannelSection.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs
index 94ee9d4bf6..c02215d690 100644
--- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs
+++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2018 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 osuTK;
@@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Chat.Selection
         public readonly FillFlowContainer<ChannelListItem> ChannelFlow;
 
         public IEnumerable<IFilterable> FilterableChildren => ChannelFlow.Children;
-        public IEnumerable<string> FilterTerms => new[] { Header };
+        public IEnumerable<string> FilterTerms => Array.Empty<string>();
         public bool MatchingFilter
         {
             set

From 3953f829c8e9b0b0247b6a43a6b0cbbfeda2bab3 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 13:29:37 +0900
Subject: [PATCH 02/43] Add letterbox/screen scaling support

---
 osu.Game/Configuration/OsuConfigManager.cs    |  15 ++-
 osu.Game/Configuration/ScalingMode.cs         |  12 ++
 .../Graphics/Containers/ScalingContainer.cs   | 122 ++++++++++++++++++
 .../Graphics/UserInterface/OsuSliderBar.cs    |   2 +-
 osu.Game/OsuGame.cs                           |  15 ++-
 osu.Game/OsuGameBase.cs                       |   6 +-
 .../Sections/Graphics/LayoutSettings.cs       |  47 ++++---
 .../Backgrounds/BackgroundScreenEmpty.cs      |  20 ++-
 osu.Game/Screens/Menu/Intro.cs                |   2 +-
 osu.Game/Screens/Menu/MenuSideFlashes.cs      |   2 +
 osu.Game/Screens/Play/Player.cs               |  16 ++-
 11 files changed, 228 insertions(+), 31 deletions(-)
 create mode 100644 osu.Game/Configuration/ScalingMode.cs
 create mode 100644 osu.Game/Graphics/Containers/ScalingContainer.cs

diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 8975ab8a0e..be293d02f6 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -96,6 +96,14 @@ namespace osu.Game.Configuration
             Set(OsuSetting.ScreenshotCaptureMenuCursor, false);
 
             Set(OsuSetting.SongSelectRightMouseScroll, false);
+
+            Set(OsuSetting.Scaling, ScalingMode.Off);
+
+            Set(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f);
+            Set(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f);
+
+            Set(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f);
+            Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
         }
 
         public OsuConfigManager(Storage storage) : base(storage)
@@ -151,6 +159,11 @@ namespace osu.Game.Configuration
         BeatmapHitsounds,
         IncreaseFirstObjectVisibility,
         ScoreDisplayMode,
-        ExternalLinkWarning
+        ExternalLinkWarning,
+        Scaling,
+        ScalingPositionX,
+        ScalingPositionY,
+        ScalingSizeX,
+        ScalingSizeY
     }
 }
diff --git a/osu.Game/Configuration/ScalingMode.cs b/osu.Game/Configuration/ScalingMode.cs
new file mode 100644
index 0000000000..063e967fa3
--- /dev/null
+++ b/osu.Game/Configuration/ScalingMode.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2007-2019 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+namespace osu.Game.Configuration
+{
+    public enum ScalingMode
+    {
+        Off,
+        Everything,
+        ExcludeOverlays,
+        Gameplay,
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
new file mode 100644
index 0000000000..6686e6057e
--- /dev/null
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -0,0 +1,122 @@
+// Copyright (c) 2007-2019 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Configuration;
+using osu.Game.Graphics.Backgrounds;
+using osuTK;
+
+namespace osu.Game.Graphics.Containers
+{
+    /// <summary>
+    /// Handles user-defined scaling, allowing application at multiple levels defined by <see cref="ScalingMode"/>.
+    /// </summary>
+    public class ScalingContainer : Container
+    {
+        private readonly bool isTopLevel;
+
+        private Bindable<float> sizeX;
+        private Bindable<float> sizeY;
+        private Bindable<float> posX;
+        private Bindable<float> posY;
+
+        private readonly ScalingMode targetMode;
+
+        private Bindable<ScalingMode> scalingMode;
+
+        private readonly Container content;
+        protected override Container<Drawable> Content => content;
+
+        private readonly Container sizableContainer;
+
+        private Drawable backgroundLayer;
+
+        /// <summary>
+        /// Create a new instance.
+        /// </summary>
+        /// <param name="targetMode">The mode which this container should be handling.</param>
+        public ScalingContainer(ScalingMode targetMode)
+        {
+            this.targetMode = targetMode;
+            RelativeSizeAxes = Axes.Both;
+
+            InternalChild = sizableContainer = new Container
+            {
+                RelativeSizeAxes = Axes.Both,
+                RelativePositionAxes = Axes.Both,
+                CornerRadius = 10,
+                Child = content = new DrawSizePreservingFillContainer()
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuConfigManager config)
+        {
+            scalingMode = config.GetBindable<ScalingMode>(OsuSetting.Scaling);
+            scalingMode.ValueChanged += _ => updateSize();
+
+            sizeX = config.GetBindable<float>(OsuSetting.ScalingSizeX);
+            sizeX.ValueChanged += _ => updateSize();
+
+            sizeY = config.GetBindable<float>(OsuSetting.ScalingSizeY);
+            sizeY.ValueChanged += _ => updateSize();
+
+            posX = config.GetBindable<float>(OsuSetting.ScalingPositionX);
+            posX.ValueChanged += _ => updateSize();
+
+            posY = config.GetBindable<float>(OsuSetting.ScalingPositionY);
+            posY.ValueChanged += _ => updateSize();
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            updateSize();
+            content.FinishTransforms();
+        }
+
+        private bool requiresBackgroundVisible => (scalingMode == ScalingMode.Everything || scalingMode == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1);
+
+        private void updateSize()
+        {
+            if (targetMode == ScalingMode.Everything)
+            {
+                // the top level scaling container manages the background to be displayed while scaling.
+                if (requiresBackgroundVisible)
+                {
+                    if (backgroundLayer == null)
+                        LoadComponentAsync(backgroundLayer = new Background("Menu/menu-background-1")
+                        {
+                            Colour = OsuColour.Gray(0.1f),
+                            Alpha = 0,
+                            Depth = float.MaxValue
+                        }, d =>
+                        {
+                            AddInternal(d);
+                            d.FadeTo(requiresBackgroundVisible ? 1 : 0, 4000, Easing.OutQuint);
+                        });
+                    else
+                        backgroundLayer.FadeIn(500);
+                }
+                else
+                    backgroundLayer?.FadeOut(500);
+            }
+
+            bool letterbox = scalingMode.Value == targetMode;
+
+            var targetSize = letterbox ? new Vector2(sizeX, sizeY) : Vector2.One;
+            var targetPosition = letterbox ? new Vector2(posX, posY) * (Vector2.One - targetSize) : Vector2.Zero;
+            bool requiresMasking = targetSize != Vector2.One;
+
+            if (requiresMasking)
+                sizableContainer.Masking = true;
+
+            sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart);
+            sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { content.Masking = requiresMasking; });
+        }
+    }
+}
diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
index a59abcbcee..10b83c2610 100644
--- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
+++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Graphics.UserInterface
                     var floatMinValue = bindableDouble?.MinValue ?? bindableFloat.MinValue;
                     var floatMaxValue = bindableDouble?.MaxValue ?? bindableFloat.MaxValue;
 
-                    if (floatMaxValue == 1 && (floatMinValue == 0 || floatMinValue == -1))
+                    if (floatMaxValue == 1 && floatMinValue >= -1)
                         return floatValue.Value.ToString("P0");
 
                     var decimalPrecision = normalise((decimal)floatPrecision, max_decimal_digits);
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 2a4c812401..c9385359bc 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -26,6 +26,7 @@ using osu.Framework.Platform;
 using osu.Framework.Threading;
 using osu.Game.Beatmaps;
 using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
 using osu.Game.Input;
 using osu.Game.Overlays.Notifications;
 using osu.Game.Rulesets;
@@ -187,6 +188,7 @@ namespace osu.Game
         }
 
         private ExternalLinkOpener externalLinkOpener;
+
         public void OpenUrlExternally(string url)
         {
             if (url.StartsWith("/"))
@@ -353,7 +355,11 @@ namespace osu.Game
                     ActionRequested = action => volume.Adjust(action),
                     ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
                 },
-                mainContent = new Container { RelativeSizeAxes = Axes.Both },
+                screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
+                {
+                    RelativeSizeAxes = Axes.Both,
+                },
+                mainContent = new DrawSizePreservingFillContainer(),
                 overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue },
                 idleTracker = new IdleTracker(6000)
             });
@@ -362,7 +368,7 @@ namespace osu.Game
             {
                 screenStack.ModePushed += screenAdded;
                 screenStack.Exited += screenRemoved;
-                mainContent.Add(screenStack);
+                screenContainer.Add(screenStack);
             });
 
             loadComponentSingleFile(Toolbar = new Toolbar
@@ -497,7 +503,7 @@ namespace osu.Game
                 if (notifications.State == Visibility.Visible)
                     offset -= ToolbarButton.WIDTH / 2;
 
-                screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint);
+                screenContainer.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint);
             }
 
             settings.StateChanged += _ => updateScreenOffset();
@@ -555,7 +561,7 @@ namespace osu.Game
                 focused.StateChanged += s =>
                 {
                     visibleOverlayCount += s == Visibility.Visible ? 1 : -1;
-                    screenStack.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
+                    screenContainer.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
                 };
             }
 
@@ -646,6 +652,7 @@ namespace osu.Game
 
         private OsuScreen currentScreen;
         private FrameworkConfigManager frameworkConfig;
+        private ScalingContainer screenContainer;
 
         protected override bool OnExiting()
         {
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 683fa30818..b6c642c9dc 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -24,6 +24,7 @@ using osu.Framework.Input;
 using osu.Framework.Logging;
 using osu.Game.Audio;
 using osu.Game.Database;
+using osu.Game.Graphics.Containers;
 using osu.Game.Input;
 using osu.Game.Input.Bindings;
 using osu.Game.IO;
@@ -189,7 +190,7 @@ namespace osu.Game
                 Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }
             };
 
-            base.Content.Add(new DrawSizePreservingFillContainer { Child = MenuCursorContainer });
+            base.Content.Add(new ScalingContainer(ScalingMode.Everything) { Child = MenuCursorContainer });
 
             KeyBindingStore.Register(globalBinding);
             dependencies.Cache(globalBinding);
@@ -247,7 +248,8 @@ namespace osu.Game
             var extension = Path.GetExtension(paths.First())?.ToLowerInvariant();
 
             foreach (var importer in fileImporters)
-                if (importer.HandledExtensions.Contains(extension)) importer.Import(paths);
+                if (importer.HandledExtensions.Contains(extension))
+                    importer.Import(paths);
         }
 
         public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray();
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 685244e06b..0386065a82 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
+using osu.Game.Configuration;
 using osu.Game.Graphics.UserInterface;
 
 namespace osu.Game.Overlays.Settings.Sections.Graphics
@@ -16,9 +17,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
     {
         protected override string Header => "Layout";
 
-        private FillFlowContainer letterboxSettings;
+        private FillFlowContainer scalingSettings;
 
-        private Bindable<bool> letterboxing;
+        private Bindable<ScalingMode> scalingMode;
         private Bindable<Size> sizeFullscreen;
 
         private OsuGameBase game;
@@ -28,11 +29,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
         private const int transition_duration = 400;
 
         [BackgroundDependencyLoader]
-        private void load(FrameworkConfigManager config, OsuGameBase game)
+        private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, OsuGameBase game)
         {
             this.game = game;
 
-            letterboxing = config.GetBindable<bool>(FrameworkSetting.Letterboxing);
+            scalingMode = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling);
             sizeFullscreen = config.GetBindable<Size>(FrameworkSetting.SizeFullscreen);
 
             Container resolutionSettingsContainer;
@@ -49,12 +50,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y
                 },
-                new SettingsCheckbox
+                new SettingsEnumDropdown<ScalingMode>
                 {
-                    LabelText = "Letterboxing",
-                    Bindable = letterboxing,
+                    LabelText = "Scaling",
+                    Bindable = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
                 },
-                letterboxSettings = new FillFlowContainer
+                scalingSettings = new FillFlowContainer
                 {
                     Direction = FillDirection.Vertical,
                     RelativeSizeAxes = Axes.X,
@@ -65,16 +66,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
 
                     Children = new Drawable[]
                     {
-                        new SettingsSlider<double>
+                        new SettingsSlider<float>
                         {
                             LabelText = "Horizontal position",
-                            Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionX),
+                            Bindable = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionX),
                             KeyboardStep = 0.01f
                         },
-                        new SettingsSlider<double>
+                        new SettingsSlider<float>
                         {
                             LabelText = "Vertical position",
-                            Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionY),
+                            Bindable = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY),
+                            KeyboardStep = 0.01f
+                        },
+                        new SettingsSlider<float>
+                        {
+                            LabelText = "Horizontal size",
+                            Bindable = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeX),
+                            KeyboardStep = 0.01f
+                        },
+                        new SettingsSlider<float>
+                        {
+                            LabelText = "Vertical size",
+                            Bindable = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeY),
                             KeyboardStep = 0.01f
                         },
                     }
@@ -105,13 +118,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                 }, true);
             }
 
-            letterboxing.BindValueChanged(isVisible =>
+            scalingMode.BindValueChanged(mode =>
             {
-                letterboxSettings.ClearTransforms();
-                letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None;
+                scalingSettings.ClearTransforms();
+                scalingSettings.AutoSizeAxes = mode != ScalingMode.Off ? Axes.Y : Axes.None;
 
-                if (!isVisible)
-                    letterboxSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
+                if (mode == ScalingMode.Off)
+                    scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
             }, true);
         }
 
diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs
index 5e08db8907..c097d25178 100644
--- a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs
+++ b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs
@@ -1,9 +1,27 @@
 // Copyright (c) 2007-2018 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.Shapes;
+using osu.Framework.Screens;
+using osuTK.Graphics;
+
 namespace osu.Game.Screens.Backgrounds
 {
-    public class BackgroundScreenEmpty : BackgroundScreen
+    public class BackgroundScreenBlack : BackgroundScreen
     {
+        public BackgroundScreenBlack()
+        {
+            Child = new Box
+            {
+                Colour = Color4.Black,
+                RelativeSizeAxes = Axes.Both,
+            };
+        }
+
+        protected override void OnEntering(Screen last)
+        {
+            Show();
+        }
     }
 }
diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs
index fa01411a0f..8d9cd8dbe9 100644
--- a/osu.Game/Screens/Menu/Intro.cs
+++ b/osu.Game/Screens/Menu/Intro.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu
 
         public override bool CursorVisible => false;
 
-        protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty();
+        protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack();
 
         private Bindable<bool> menuVoice;
         private Bindable<bool> menuMusic;
diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index ec5528b13f..188e95ced5 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -58,6 +58,7 @@ namespace osu.Game.Screens.Menu
                     Origin = Anchor.CentreLeft,
                     RelativeSizeAxes = Axes.Y,
                     Width = box_width * 2,
+                    Height = 1.5f,
                     // align off-screen to make sure our edges don't become visible during parallax.
                     X = -box_width,
                     Alpha = 0,
@@ -70,6 +71,7 @@ namespace osu.Game.Screens.Menu
                     Origin = Anchor.CentreRight,
                     RelativeSizeAxes = Axes.Y,
                     Width = box_width * 2,
+                    Height = 1.5f,
                     X = box_width,
                     Alpha = 0,
                     Blending = BlendingMode.Additive,
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index c102fb0223..93102228a4 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -20,6 +20,7 @@ using osu.Framework.Timing;
 using osu.Game.Beatmaps;
 using osu.Game.Configuration;
 using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.Cursor;
 using osu.Game.Online.API;
 using osu.Game.Overlays;
@@ -179,10 +180,14 @@ namespace osu.Game.Screens.Play
                             RelativeSizeAxes = Axes.Both,
                             Alpha = 0,
                         },
-                        new LocalSkinOverrideContainer(working.Skin)
+                        new ScalingContainer(ScalingMode.Gameplay)
                         {
-                            RelativeSizeAxes = Axes.Both,
-                            Child = RulesetContainer
+                            Child =
+                                new LocalSkinOverrideContainer(working.Skin)
+                                {
+                                    RelativeSizeAxes = Axes.Both,
+                                    Child = RulesetContainer
+                                }
                         },
                         new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
                         {
@@ -191,7 +196,10 @@ namespace osu.Game.Screens.Play
                             ProcessCustomClock = false,
                             Breaks = beatmap.Breaks
                         },
-                        RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
+                        new ScalingContainer(ScalingMode.Gameplay)
+                        {
+                            Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
+                        },
                         hudOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
                         {
                             Clock = Clock, // hud overlay doesn't want to use the audio clock directly

From 5a807f2143893e91459f22d22e3d28cfa942e160 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 14:18:29 +0900
Subject: [PATCH 03/43] Add OSD support

---
 osu.Game/Configuration/OsuConfigManager.cs | 25 ++++++++++++++++++----
 osu.Game/Configuration/ScalingMode.cs      |  7 +++++-
 2 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index be293d02f6..aced8e3024 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -1,8 +1,10 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System;
 using osu.Framework.Configuration;
 using osu.Framework.Configuration.Tracking;
+using osu.Framework.Extensions;
 using osu.Framework.Platform;
 using osu.Game.Overlays;
 using osu.Game.Rulesets.Scoring;
@@ -106,14 +108,29 @@ namespace osu.Game.Configuration
             Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
         }
 
-        public OsuConfigManager(Storage storage) : base(storage)
+        public OsuConfigManager(Storage storage)
+            : base(storage)
         {
         }
 
-        public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
+        public override TrackedSettings CreateTrackedSettings()
         {
-            new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled"))
-        };
+            Func<SettingDescription> scalingDescription = () =>
+            {
+                var scalingMode = Get<ScalingMode>(OsuSetting.Scaling);
+                return new SettingDescription(scalingMode, "scaling", scalingMode.GetDescription());
+            };
+
+            return new TrackedSettings
+            {
+                new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")),
+                new TrackedSetting<ScalingMode>(OsuSetting.Scaling, _ => scalingDescription()),
+                new TrackedSetting<float>(OsuSetting.ScalingSizeX, _ => scalingDescription()),
+                new TrackedSetting<float>(OsuSetting.ScalingSizeY, _ => scalingDescription()),
+                new TrackedSetting<float>(OsuSetting.ScalingPositionX, _ => scalingDescription()),
+                new TrackedSetting<float>(OsuSetting.ScalingPositionY, _ => scalingDescription()),
+            };
+        }
     }
 
     public enum OsuSetting
diff --git a/osu.Game/Configuration/ScalingMode.cs b/osu.Game/Configuration/ScalingMode.cs
index 063e967fa3..9673cc9251 100644
--- a/osu.Game/Configuration/ScalingMode.cs
+++ b/osu.Game/Configuration/ScalingMode.cs
@@ -1,12 +1,17 @@
 // Copyright (c) 2007-2019 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.Configuration
 {
     public enum ScalingMode
     {
+
         Off,
         Everything,
+        [Description("Excluding overlays")]
         ExcludeOverlays,
         Gameplay,
     }
-}
\ No newline at end of file
+}

From 35a6257642ed7600ee85cf069735b1ddf7246340 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 14:55:59 +0900
Subject: [PATCH 04/43] Delay updates when changes would affect mouse position

---
 .../Sections/Graphics/LayoutSettings.cs       | 55 +++++++++++++++++--
 1 file changed, 51 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 0386065a82..13c4156db6 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2018 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.Drawing;
 using System.Linq;
@@ -8,8 +9,10 @@ using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
+using osu.Framework.Threading;
 using osu.Game.Configuration;
 using osu.Game.Graphics.UserInterface;
+using osuTK.Input;
 
 namespace osu.Game.Overlays.Settings.Sections.Graphics
 {
@@ -26,6 +29,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
         private SettingsDropdown<Size> resolutionDropdown;
         private SettingsEnumDropdown<WindowMode> windowModeDropdown;
 
+        private Bindable<float> scalingPositionX;
+        private Bindable<float> scalingPositionY;
+        private Bindable<float> scalingSizeX;
+        private Bindable<float> scalingSizeY;
+
         private const int transition_duration = 400;
 
         [BackgroundDependencyLoader]
@@ -35,6 +43,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
 
             scalingMode = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling);
             sizeFullscreen = config.GetBindable<Size>(FrameworkSetting.SizeFullscreen);
+            scalingSizeX = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeX);
+            scalingSizeY = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeY);
+            scalingPositionX = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionX);
+            scalingPositionY = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY);
 
             Container resolutionSettingsContainer;
 
@@ -69,25 +81,25 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                         new SettingsSlider<float>
                         {
                             LabelText = "Horizontal position",
-                            Bindable = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionX),
+                            Bindable = delayedBindable(scalingPositionX),
                             KeyboardStep = 0.01f
                         },
                         new SettingsSlider<float>
                         {
                             LabelText = "Vertical position",
-                            Bindable = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY),
+                            Bindable = delayedBindable(scalingPositionY),
                             KeyboardStep = 0.01f
                         },
                         new SettingsSlider<float>
                         {
                             LabelText = "Horizontal size",
-                            Bindable = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeX),
+                            Bindable = delayedBindable(scalingSizeX),
                             KeyboardStep = 0.01f
                         },
                         new SettingsSlider<float>
                         {
                             LabelText = "Vertical size",
-                            Bindable = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeY),
+                            Bindable = delayedBindable(scalingSizeY),
                             KeyboardStep = 0.01f
                         },
                     }
@@ -128,6 +140,41 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
             }, true);
         }
 
+        /// <summary>
+        /// Create a delayed bindable which only updates when a condition is met.
+        /// </summary>
+        /// <param name="configBindable">The config bindable.</param>
+        /// <returns>A bindable which will propagate updates with a delay.</returns>
+        private Bindable<float> delayedBindable(Bindable<float> configBindable)
+        {
+            var delayed = new BindableFloat { MinValue = 0, MaxValue = 1, Default = configBindable.Default };
+
+            configBindable.BindValueChanged(v => delayed.Value = v, true);
+            delayed.ValueChanged += v =>
+            {
+                if (scalingMode == ScalingMode.Everything)
+                    applyWithDelay(() => configBindable.Value = v);
+                else
+                    configBindable.Value = v;
+            };
+
+            return delayed;
+        }
+
+        private ScheduledDelegate delayedApplication;
+
+        private void applyWithDelay(Action func, bool firstRun = true)
+        {
+            if (!firstRun && !GetContainingInputManager().CurrentState.Mouse.IsPressed(MouseButton.Left))
+            {
+                func();
+                return;
+            }
+
+            delayedApplication?.Cancel();
+            delayedApplication = Scheduler.AddDelayed(() => applyWithDelay(func, false), 250);
+        }
+
         private IReadOnlyList<Size> getResolutions()
         {
             var resolutions = new List<Size> { new Size(9999, 9999) };

From 9c7830d83bf4b88608e5c9de04e77773f6ae4cad Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 14:58:44 +0900
Subject: [PATCH 05/43] Size -> scale

---
 .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs    | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 13c4156db6..3b0de5db10 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -75,7 +75,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     AutoSizeDuration = transition_duration,
                     AutoSizeEasing = Easing.OutQuint,
                     Masking = true,
-
                     Children = new Drawable[]
                     {
                         new SettingsSlider<float>
@@ -92,13 +91,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                         },
                         new SettingsSlider<float>
                         {
-                            LabelText = "Horizontal size",
+                            LabelText = "Horizontal scale",
                             Bindable = delayedBindable(scalingSizeX),
                             KeyboardStep = 0.01f
                         },
                         new SettingsSlider<float>
                         {
-                            LabelText = "Vertical size",
+                            LabelText = "Vertical scale",
                             Bindable = delayedBindable(scalingSizeY),
                             KeyboardStep = 0.01f
                         },

From 3a10dd47d5c8fdc7d01d1f32f8d53691aef7a16f Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 15:28:35 +0900
Subject: [PATCH 06/43] Add preview for gameplay region

---
 .../Graphics/Containers/ScalingContainer.cs   | 15 +++----
 .../Sections/Graphics/LayoutSettings.cs       | 41 +++++++++++++++++--
 2 files changed, 45 insertions(+), 11 deletions(-)

diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index 6686e6057e..0fba88bb28 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Graphics.Containers
         private Bindable<float> posX;
         private Bindable<float> posY;
 
-        private readonly ScalingMode targetMode;
+        private readonly ScalingMode? targetMode;
 
         private Bindable<ScalingMode> scalingMode;
 
@@ -37,8 +37,8 @@ namespace osu.Game.Graphics.Containers
         /// <summary>
         /// Create a new instance.
         /// </summary>
-        /// <param name="targetMode">The mode which this container should be handling.</param>
-        public ScalingContainer(ScalingMode targetMode)
+        /// <param name="targetMode">The mode which this container should be handling. Handles all modes if null.</param>
+        public ScalingContainer(ScalingMode? targetMode = null)
         {
             this.targetMode = targetMode;
             RelativeSizeAxes = Axes.Both;
@@ -47,6 +47,7 @@ namespace osu.Game.Graphics.Containers
             {
                 RelativeSizeAxes = Axes.Both,
                 RelativePositionAxes = Axes.Both,
+                Masking = true,
                 CornerRadius = 10,
                 Child = content = new DrawSizePreservingFillContainer()
             };
@@ -76,7 +77,7 @@ namespace osu.Game.Graphics.Containers
             base.LoadComplete();
 
             updateSize();
-            content.FinishTransforms();
+            sizableContainer.FinishTransforms();
         }
 
         private bool requiresBackgroundVisible => (scalingMode == ScalingMode.Everything || scalingMode == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1);
@@ -106,10 +107,10 @@ namespace osu.Game.Graphics.Containers
                     backgroundLayer?.FadeOut(500);
             }
 
-            bool letterbox = scalingMode.Value == targetMode;
+            bool scaling = targetMode == null || scalingMode.Value == targetMode;
 
-            var targetSize = letterbox ? new Vector2(sizeX, sizeY) : Vector2.One;
-            var targetPosition = letterbox ? new Vector2(posX, posY) * (Vector2.One - targetSize) : Vector2.Zero;
+            var targetSize = scaling ? new Vector2(sizeX, sizeY) : Vector2.One;
+            var targetPosition = scaling ? new Vector2(posX, posY) * (Vector2.One - targetSize) : Vector2.Zero;
             bool requiresMasking = targetSize != Vector2.One;
 
             if (requiresMasking)
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 3b0de5db10..9a55e97452 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -9,9 +9,12 @@ using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
 using osu.Framework.Threading;
 using osu.Game.Configuration;
+using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.UserInterface;
+using osuTK.Graphics;
 using osuTK.Input;
 
 namespace osu.Game.Overlays.Settings.Sections.Graphics
@@ -151,15 +154,32 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
             configBindable.BindValueChanged(v => delayed.Value = v, true);
             delayed.ValueChanged += v =>
             {
-                if (scalingMode == ScalingMode.Everything)
-                    applyWithDelay(() => configBindable.Value = v);
-                else
-                    configBindable.Value = v;
+                switch (scalingMode.Value)
+                {
+                    case ScalingMode.Everything:
+                        applyWithDelay(() => configBindable.Value = v);
+                        return;
+                    case ScalingMode.Gameplay:
+                        showPreview();
+                        break;
+                }
+
+                configBindable.Value = v;
             };
 
             return delayed;
         }
 
+        private Drawable preview;
+        private void showPreview()
+        {
+            if (preview?.IsAlive != true)
+                game.Add(preview = new ScalingPreview());
+
+            preview.FadeOutFromOne(1500);
+            preview.Expire();
+        }
+
         private ScheduledDelegate delayedApplication;
 
         private void applyWithDelay(Action func, bool firstRun = true)
@@ -191,6 +211,19 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
             return resolutions;
         }
 
+        private class ScalingPreview : ScalingContainer
+        {
+            public ScalingPreview()
+            {
+                Child = new Box
+                {
+                    Colour = Color4.White,
+                    RelativeSizeAxes = Axes.Both,
+                    Alpha = 0.5f,
+                };
+            }
+        }
+
         private class ResolutionSettingsDropdown : SettingsDropdown<Size>
         {
             protected override OsuDropdown<Size> CreateDropdown() => new ResolutionDropdownControl { Items = Items };

From 4c3310ca8026da130cee2ff679c98e4dc5d0d00c Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 15:28:48 +0900
Subject: [PATCH 07/43] Remove unnecessary tracked settings (for now)

---
 osu.Game/Configuration/OsuConfigManager.cs | 19 +++----------------
 1 file changed, 3 insertions(+), 16 deletions(-)

diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index aced8e3024..46b51024f2 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -1,7 +1,6 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using System;
 using osu.Framework.Configuration;
 using osu.Framework.Configuration.Tracking;
 using osu.Framework.Extensions;
@@ -113,24 +112,12 @@ namespace osu.Game.Configuration
         {
         }
 
-        public override TrackedSettings CreateTrackedSettings()
-        {
-            Func<SettingDescription> scalingDescription = () =>
-            {
-                var scalingMode = Get<ScalingMode>(OsuSetting.Scaling);
-                return new SettingDescription(scalingMode, "scaling", scalingMode.GetDescription());
-            };
-
-            return new TrackedSettings
+        public override TrackedSettings CreateTrackedSettings() =>
+            new TrackedSettings
             {
                 new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")),
-                new TrackedSetting<ScalingMode>(OsuSetting.Scaling, _ => scalingDescription()),
-                new TrackedSetting<float>(OsuSetting.ScalingSizeX, _ => scalingDescription()),
-                new TrackedSetting<float>(OsuSetting.ScalingSizeY, _ => scalingDescription()),
-                new TrackedSetting<float>(OsuSetting.ScalingPositionX, _ => scalingDescription()),
-                new TrackedSetting<float>(OsuSetting.ScalingPositionY, _ => scalingDescription()),
+                new TrackedSetting<ScalingMode>(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())),
             };
-        }
     }
 
     public enum OsuSetting

From c528a3896dbcc9a3ef1910eade2943a8665c2746 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 15:34:32 +0900
Subject: [PATCH 08/43] Formatting and naming

---
 osu.Game/Configuration/OsuConfigManager.cs            | 11 +++++------
 osu.Game/Configuration/ScalingMode.cs                 |  1 -
 ...kgroundScreenEmpty.cs => BackgroundScreenBlack.cs} |  0
 osu.Game/Screens/Play/Player.cs                       | 11 +++++------
 4 files changed, 10 insertions(+), 13 deletions(-)
 rename osu.Game/Screens/Backgrounds/{BackgroundScreenEmpty.cs => BackgroundScreenBlack.cs} (100%)

diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 46b51024f2..8df286ffb2 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -112,12 +112,11 @@ namespace osu.Game.Configuration
         {
         }
 
-        public override TrackedSettings CreateTrackedSettings() =>
-            new TrackedSettings
-            {
-                new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")),
-                new TrackedSetting<ScalingMode>(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())),
-            };
+        public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
+        {
+            new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")),
+            new TrackedSetting<ScalingMode>(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())),
+        };
     }
 
     public enum OsuSetting
diff --git a/osu.Game/Configuration/ScalingMode.cs b/osu.Game/Configuration/ScalingMode.cs
index 9673cc9251..4d15fe8b4b 100644
--- a/osu.Game/Configuration/ScalingMode.cs
+++ b/osu.Game/Configuration/ScalingMode.cs
@@ -7,7 +7,6 @@ namespace osu.Game.Configuration
 {
     public enum ScalingMode
     {
-
         Off,
         Everything,
         [Description("Excluding overlays")]
diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs
similarity index 100%
rename from osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs
rename to osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 93102228a4..20cc80a104 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -182,12 +182,11 @@ namespace osu.Game.Screens.Play
                         },
                         new ScalingContainer(ScalingMode.Gameplay)
                         {
-                            Child =
-                                new LocalSkinOverrideContainer(working.Skin)
-                                {
-                                    RelativeSizeAxes = Axes.Both,
-                                    Child = RulesetContainer
-                                }
+                            Child = new LocalSkinOverrideContainer(working.Skin)
+                            {
+                                RelativeSizeAxes = Axes.Both,
+                                Child = RulesetContainer
+                            }
                         },
                         new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
                         {

From f2ccf70d1ba97a8c8f174f89ed06d198b89184f1 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 15:37:27 +0900
Subject: [PATCH 09/43] Backdate license header for now

---
 osu.Game/Configuration/ScalingMode.cs            | 2 +-
 osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Configuration/ScalingMode.cs b/osu.Game/Configuration/ScalingMode.cs
index 4d15fe8b4b..b907d55d82 100644
--- a/osu.Game/Configuration/ScalingMode.cs
+++ b/osu.Game/Configuration/ScalingMode.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2019 ppy Pty Ltd <contact@ppy.sh>.
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.ComponentModel;
diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index 0fba88bb28..4dc25ae3d1 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2019 ppy Pty Ltd <contact@ppy.sh>.
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Framework.Allocation;

From 4a7c6fb19d9c037f1557496c3b989dd8e53a73aa Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 4 Jan 2019 16:33:35 +0900
Subject: [PATCH 10/43] Fix PP not display on profile overlay

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

diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs
index b863566967..78cc3592c7 100644
--- a/osu.Game/Scoring/ScoreInfo.cs
+++ b/osu.Game/Scoring/ScoreInfo.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Scoring
         [Column(TypeName="DECIMAL(1,4)")]
         public double Accuracy { get; set; }
 
-        [JsonIgnore]
+        [JsonProperty(@"pp")]
         public double? PP { get; set; }
 
         [JsonProperty("max_combo")]

From 4b5fc8587528ecde0fe4eeeb50f4f73103036690 Mon Sep 17 00:00:00 2001
From: Roman Kapustin <TocoToucanMS@gmail.com>
Date: Sat, 5 Jan 2019 19:35:33 +0300
Subject: [PATCH 11/43] Use Find instead of FirstOrDefault

---
 osu.Desktop/Updater/SimpleUpdateManager.cs                   | 5 ++---
 osu.Game.Rulesets.Osu/Objects/Slider.cs                      | 2 +-
 osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs   | 2 +-
 osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs             | 2 +-
 osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs         | 2 +-
 osu.Game/Beatmaps/BeatmapSetInfo.cs                          | 2 +-
 osu.Game/Configuration/DatabasedConfigManager.cs             | 3 +--
 osu.Game/OsuGame.cs                                          | 2 +-
 osu.Game/Screens/Edit/EditorClock.cs                         | 4 ++--
 osu.Game/Screens/Tournament/Drawings.cs                      | 2 +-
 osu.Game/Skinning/LegacySkin.cs                              | 2 +-
 .../Storyboards/Drawables/DrawableStoryboardAnimation.cs     | 3 +--
 osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs   | 3 +--
 13 files changed, 15 insertions(+), 19 deletions(-)

diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs
index e404ccd2b3..6956eb30b0 100644
--- a/osu.Desktop/Updater/SimpleUpdateManager.cs
+++ b/osu.Desktop/Updater/SimpleUpdateManager.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 System.Threading.Tasks;
 using Newtonsoft.Json;
 using osu.Framework;
@@ -77,10 +76,10 @@ namespace osu.Desktop.Updater
             switch (RuntimeInfo.OS)
             {
                 case RuntimeInfo.Platform.Windows:
-                    bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".exe"));
+                    bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe"));
                     break;
                 case RuntimeInfo.Platform.MacOsx:
-                    bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".app.zip"));
+                    bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip"));
                     break;
             }
 
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 2af1de7355..44185fb83a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Objects
                     var distanceProgress = d / length;
                     var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
 
-                    var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL)
+                    var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL)
                                       ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
                     var sampleList = new List<SampleInfo>();
 
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 1412bd3cea..153e5733e5 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
         {
             if (userTriggered)
             {
-                var nextTick = ticks.FirstOrDefault(j => !j.IsHit);
+                var nextTick = ticks.Find(j => !j.IsHit);
 
                 nextTick?.TriggerResult(HitResult.Great);
 
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
index db66c01814..f156728981 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
@@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual
 
         private bool selectedBeatmapVisible()
         {
-            var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected);
+            var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected);
             if (currentlySelected == null)
                 return true;
             return currentlySelected.Item.Visible;
diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
index 2c56f08f42..0e092276a1 100644
--- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual
 
             if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
             {
-                var p = progressingNotifications.FirstOrDefault(n => n.State == ProgressNotificationState.Queued);
+                var p = progressingNotifications.Find(n => n.State == ProgressNotificationState.Queued);
                 if (p != null)
                     p.State = ProgressNotificationState.Active;
             }
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index 8c541e9344..a5399ce8c7 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps
 
         public string Hash { get; set; }
 
-        public string StoryboardFile => Files?.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
+        public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb"))?.Filename;
 
         public List<BeatmapSetFileInfo> Files { get; set; }
 
diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs
index 0ede6de0f2..334fed2b5a 100644
--- a/osu.Game/Configuration/DatabasedConfigManager.cs
+++ b/osu.Game/Configuration/DatabasedConfigManager.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.Game.Rulesets;
 
@@ -43,7 +42,7 @@ namespace osu.Game.Configuration
         {
             base.AddBindable(lookup, bindable);
 
-            var setting = databasedSettings.FirstOrDefault(s => (int)s.Key == (int)(object)lookup);
+            var setting = databasedSettings.Find(s => (int)s.Key == (int)(object)lookup);
             if (setting != null)
             {
                 bindable.Parse(setting.Value);
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 2a4c812401..702b1ae108 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -222,7 +222,7 @@ namespace osu.Game
                 var databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID);
 
                 // Use first beatmap available for current ruleset, else switch ruleset.
-                var first = databasedSet.Beatmaps.FirstOrDefault(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
+                var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
 
                 ruleset.Value = first.Ruleset;
                 Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs
index aa30b1a9f5..05ba1ab732 100644
--- a/osu.Game/Screens/Edit/EditorClock.cs
+++ b/osu.Game/Screens/Edit/EditorClock.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit
 
             // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to
             // the next timing point's start time
-            var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time);
+            var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time);
             if (position > nextTimingPoint?.Time)
                 position = nextTimingPoint.Time;
 
@@ -123,7 +123,7 @@ namespace osu.Game.Screens.Edit
             if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
                 seekTime = timingPoint.Time;
 
-            var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time);
+            var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time);
             if (seekTime > nextTimingPoint?.Time)
                 seekTime = nextTimingPoint.Time;
 
diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs
index 0a428ea289..754f34f00f 100644
--- a/osu.Game/Screens/Tournament/Drawings.cs
+++ b/osu.Game/Screens/Tournament/Drawings.cs
@@ -327,7 +327,7 @@ namespace osu.Game.Screens.Tournament
                                 continue;
 
                             // ReSharper disable once AccessToModifiedClosure
-                            DrawingsTeam teamToAdd = allTeams.FirstOrDefault(t => t.FullName == line);
+                            DrawingsTeam teamToAdd = allTeams.Find(t => t.FullName == line);
 
                             if (teamToAdd == null)
                                 continue;
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 25a9813ad6..23367c58c9 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Skinning
 
                 string lastPiece = filename.Split('/').Last();
 
-                var file = source.Files.FirstOrDefault(f =>
+                var file = source.Files.Find(f =>
                     string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), lastPiece, StringComparison.InvariantCultureIgnoreCase));
                 return file?.FileInfo.StoragePath;
             }
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
index 8f860c5f53..2330c25443 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
@@ -6,7 +6,6 @@ using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Animations;
 using osu.Framework.Graphics.Textures;
-using System.Linq;
 using osu.Game.Beatmaps;
 
 namespace osu.Game.Storyboards.Drawables
@@ -71,7 +70,7 @@ namespace osu.Game.Storyboards.Drawables
             {
                 var framePath = basePath.Replace(".", frame + ".");
 
-                var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath;
+                var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath;
                 if (path == null)
                     continue;
 
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs
index 485f9bf61c..b03285b419 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs
@@ -6,7 +6,6 @@ using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Graphics.Textures;
-using System.Linq;
 using osu.Game.Beatmaps;
 
 namespace osu.Game.Storyboards.Drawables
@@ -66,7 +65,7 @@ namespace osu.Game.Storyboards.Drawables
         private void load(IBindableBeatmap beatmap, TextureStore textureStore)
         {
             var spritePath = Sprite.Path.ToLowerInvariant();
-            var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath;
+            var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath;
             if (path == null)
                 return;
 

From 469a64a7cd4f5580da672b642e971207f02781d2 Mon Sep 17 00:00:00 2001
From: Roman Kapustin <TocoToucanMS@gmail.com>
Date: Sat, 5 Jan 2019 21:03:03 +0300
Subject: [PATCH 12/43] The code must not contain multiple blank lines in a row

---
 osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs | 2 --
 1 file changed, 2 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
index 0e092276a1..66ae2d3012 100644
--- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
@@ -49,7 +49,6 @@ namespace osu.Game.Tests.Visual
 
             manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; };
 
-
             setState(Visibility.Visible);
             AddStep(@"simple #1", sendHelloNotification);
             AddStep(@"simple #2", sendAmazingNotification);
@@ -75,7 +74,6 @@ namespace osu.Game.Tests.Visual
 
             checkProgressingCount(0);
 
-
             setState(Visibility.Visible);
 
             //AddStep(@"barrage", () => sendBarrage());

From 73c2fcac0894c918347cbaf4e97c851e15062d6b Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 7 Jan 2019 17:03:59 +0900
Subject: [PATCH 13/43] Remove broken SettingsLabel class

---
 osu.Game/Overlays/Settings/SettingsLabel.cs | 20 --------------------
 1 file changed, 20 deletions(-)
 delete mode 100644 osu.Game/Overlays/Settings/SettingsLabel.cs

diff --git a/osu.Game/Overlays/Settings/SettingsLabel.cs b/osu.Game/Overlays/Settings/SettingsLabel.cs
deleted file mode 100644
index 2df4073191..0000000000
--- a/osu.Game/Overlays/Settings/SettingsLabel.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Game.Graphics;
-
-namespace osu.Game.Overlays.Settings
-{
-    public class SettingsLabel : SettingsItem<string>
-    {
-        protected override Drawable CreateControl() => null;
-
-        [BackgroundDependencyLoader]
-        private void load(OsuColour colour)
-        {
-            Colour = colour.Gray6;
-        }
-    }
-}

From 1c5ab6a9066427c37b6b4518a0a705b7d7bba9b2 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 Jan 2019 17:56:31 +0900
Subject: [PATCH 14/43] Implement stacking by index

---
 .../Beatmaps/OsuBeatmapProcessor.cs           | 24 ++++++++++++-------
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index db80948c94..d9640d51ce 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System;
 using osu.Framework.Graphics;
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Objects.Types;
@@ -28,16 +29,21 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                 h.StackHeight = 0;
 
             if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
-                applyStacking(osuBeatmap);
+                applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1);
             else
                 applyStackingOld(osuBeatmap);
         }
 
-        private void applyStacking(Beatmap<OsuHitObject> beatmap)
+        private void applyStacking(Beatmap<OsuHitObject> beatmap, int startIndex, int endIndex)
         {
+            if (startIndex > endIndex) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be > {nameof(endIndex)}.");
+            if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be < 0.");
+            if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be < 0.");
+
+            int extendedEndIndex = endIndex;
+
             // Extend the end index to include objects they are stacked on
-            int extendedEndIndex = beatmap.HitObjects.Count - 1;
-            for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--)
+            for (int i = endIndex; i >= startIndex; i--)
             {
                 int stackBaseIndex = i;
                 for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++)
@@ -56,8 +62,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                         //We are no longer within stacking range of the next object.
                         break;
 
-                    if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance ||
-                        stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance)
+                    if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance
+                        || stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance)
                     {
                         stackBaseIndex = n;
 
@@ -75,8 +81,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
             }
 
             //Reverse pass for stack calculation.
-            int extendedStartIndex = 0;
-            for (int i = extendedEndIndex; i > 0; i--)
+            int extendedStartIndex = startIndex;
+            for (int i = extendedEndIndex; i > startIndex; i--)
             {
                 int n = i;
                 /* We should check every note which has not yet got a stack.
@@ -155,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                     /* We have hit the first slider in a possible stack.
                         * From this point on, we ALWAYS stack positive regardless.
                         */
-                    while (--n >= 0)
+                    while (--n >= startIndex)
                     {
                         OsuHitObject objectN = beatmap.HitObjects[n];
                         if (objectN is Spinner) continue;

From 351ab6e9bdc290714dd79bdbe1585099d239d090 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 Jan 2019 17:57:45 +0900
Subject: [PATCH 15/43] Shortcut O(n^2) calculation

---
 .../Beatmaps/OsuBeatmapProcessor.cs           | 62 ++++++++++---------
 1 file changed, 32 insertions(+), 30 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index d9640d51ce..c1a9d2bafe 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -41,42 +41,44 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
             if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be < 0.");
 
             int extendedEndIndex = endIndex;
-
-            // Extend the end index to include objects they are stacked on
-            for (int i = endIndex; i >= startIndex; i--)
+            if (endIndex < beatmap.HitObjects.Count - 1)
             {
-                int stackBaseIndex = i;
-                for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++)
+                // Extend the end index to include objects they are stacked on
+                for (int i = endIndex; i >= startIndex; i--)
                 {
-                    OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex];
-                    if (stackBaseObject is Spinner) break;
-
-                    OsuHitObject objectN = beatmap.HitObjects[n];
-                    if (objectN is Spinner)
-                        continue;
-
-                    double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
-                    double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
-
-                    if (objectN.StartTime - endTime > stackThreshold)
-                        //We are no longer within stacking range of the next object.
-                        break;
-
-                    if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance
-                        || stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance)
+                    int stackBaseIndex = i;
+                    for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++)
                     {
-                        stackBaseIndex = n;
+                        OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex];
+                        if (stackBaseObject is Spinner) break;
 
-                        // HitObjects after the specified update range haven't been reset yet
-                        objectN.StackHeight = 0;
+                        OsuHitObject objectN = beatmap.HitObjects[n];
+                        if (objectN is Spinner)
+                            continue;
+
+                        double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
+                        double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
+
+                        if (objectN.StartTime - endTime > stackThreshold)
+                            //We are no longer within stacking range of the next object.
+                            break;
+
+                        if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance
+                            || stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance)
+                        {
+                            stackBaseIndex = n;
+
+                            // HitObjects after the specified update range haven't been reset yet
+                            objectN.StackHeight = 0;
+                        }
                     }
-                }
 
-                if (stackBaseIndex > extendedEndIndex)
-                {
-                    extendedEndIndex = stackBaseIndex;
-                    if (extendedEndIndex == beatmap.HitObjects.Count - 1)
-                        break;
+                    if (stackBaseIndex > extendedEndIndex)
+                    {
+                        extendedEndIndex = stackBaseIndex;
+                        if (extendedEndIndex == beatmap.HitObjects.Count - 1)
+                            break;
+                    }
                 }
             }
 

From c6c22b352fe51d75432f7e958a40bd3c3a670cd7 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 Jan 2019 18:13:40 +0900
Subject: [PATCH 16/43] More verbose exceptions

---
 osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index c1a9d2bafe..324ba0dcc7 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -36,9 +36,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
 
         private void applyStacking(Beatmap<OsuHitObject> beatmap, int startIndex, int endIndex)
         {
-            if (startIndex > endIndex) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be > {nameof(endIndex)}.");
-            if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be < 0.");
-            if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be < 0.");
+            if (startIndex > endIndex) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be greater than {nameof(endIndex)}.");
+            if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be less than 0.");
+            if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0.");
 
             int extendedEndIndex = endIndex;
             if (endIndex < beatmap.HitObjects.Count - 1)

From b57c1af084ab1cec4d68a1c5aa705df0ce5596bd Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 Jan 2019 18:15:44 +0900
Subject: [PATCH 17/43] Only update stacking if there are hitobjects

---
 .../Beatmaps/OsuBeatmapProcessor.cs             | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index 324ba0dcc7..483ff2d409 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -24,14 +24,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
 
             var osuBeatmap = (Beatmap<OsuHitObject>)Beatmap;
 
-            // Reset stacking
-            foreach (var h in osuBeatmap.HitObjects)
-                h.StackHeight = 0;
+            if (osuBeatmap.HitObjects.Count > 0)
+            {
+                // Reset stacking
+                foreach (var h in osuBeatmap.HitObjects)
+                    h.StackHeight = 0;
 
-            if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
-                applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1);
-            else
-                applyStackingOld(osuBeatmap);
+                if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
+                    applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1);
+                else
+                    applyStackingOld(osuBeatmap);
+            }
         }
 
         private void applyStacking(Beatmap<OsuHitObject> beatmap, int startIndex, int endIndex)

From f6018294b58c13608eaa45b96b6f23f46e70fa98 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 7 Jan 2019 18:50:27 +0900
Subject: [PATCH 18/43] Update framework

---
 osu.Game.Tests/Visual/TestCaseChatLink.cs     |  2 +-
 .../Visual/TestCaseLoungeRoomsContainer.cs    |  4 +-
 .../Visual/TestCaseMatchSettingsOverlay.cs    |  2 +-
 osu.Game/Beatmaps/WorkingBeatmap.cs           |  2 +-
 osu.Game/Online/Chat/ChannelManager.cs        |  8 ++--
 osu.Game/Online/Multiplayer/PlaylistItem.cs   |  4 +-
 osu.Game/Online/Multiplayer/Room.cs           |  2 +-
 .../Sections/Graphics/LayoutSettings.cs       | 43 -------------------
 osu.Game/Screens/Multi/IRoomManager.cs        |  2 +-
 .../Multi/Lounge/Components/RoomsContainer.cs |  2 +-
 osu.Game/Screens/Multi/RoomBindings.cs        |  2 +-
 osu.Game/Screens/Multi/RoomManager.cs         |  4 +-
 osu.Game/osu.Game.csproj                      |  2 +-
 13 files changed, 18 insertions(+), 61 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs
index 61c2f47e7d..8aa3283af7 100644
--- a/osu.Game.Tests/Visual/TestCaseChatLink.cs
+++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual
             linkColour = colours.Blue;
 
             var chatManager = new ChannelManager();
-            BindableCollection<Channel> availableChannels = (BindableCollection<Channel>)chatManager.AvailableChannels;
+            BindableList<Channel> availableChannels = (BindableList<Channel>)chatManager.AvailableChannels;
             availableChannels.Add(new Channel { Name = "#english"});
             availableChannels.Add(new Channel { Name = "#japanese" });
             Dependencies.Cache(chatManager);
diff --git a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs
index 3e9f2fb3a4..6b5bc875f1 100644
--- a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs
@@ -73,8 +73,8 @@ namespace osu.Game.Tests.Visual
         {
             public event Action RoomsUpdated;
 
-            public readonly BindableCollection<Room> Rooms = new BindableCollection<Room>();
-            IBindableCollection<Room> IRoomManager.Rooms => Rooms;
+            public readonly BindableList<Room> Rooms = new BindableList<Room>();
+            IBindableList<Room> IRoomManager.Rooms => Rooms;
 
             public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => Rooms.Add(room);
 
diff --git a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs
index 7fb9d4dded..6f084def48 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs
@@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual
 
             public event Action RoomsUpdated;
 
-            public IBindableCollection<Room> Rooms { get; } = null;
+            public IBindableList<Room> Rooms { get; } = null;
 
             public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
             {
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 5b76122616..e65409a1d1 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps
 
         public bool WaveformLoaded => waveform.IsResultAvailable;
         public Waveform Waveform => waveform.Value;
-        protected virtual Waveform GetWaveform() => new Waveform();
+        protected virtual Waveform GetWaveform() => new Waveform(null);
         private readonly RecyclableLazy<Waveform> waveform;
 
         public bool StoryboardLoaded => storyboard.IsResultAvailable;
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 4241b47cd3..d5deda960c 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -29,8 +29,8 @@ namespace osu.Game.Online.Chat
             @"#lobby"
         };
 
-        private readonly BindableCollection<Channel> availableChannels = new BindableCollection<Channel>();
-        private readonly BindableCollection<Channel> joinedChannels = new BindableCollection<Channel>();
+        private readonly BindableList<Channel> availableChannels = new BindableList<Channel>();
+        private readonly BindableList<Channel> joinedChannels = new BindableList<Channel>();
 
         /// <summary>
         /// The currently opened channel
@@ -40,12 +40,12 @@ namespace osu.Game.Online.Chat
         /// <summary>
         /// The Channels the player has joined
         /// </summary>
-        public IBindableCollection<Channel> JoinedChannels => joinedChannels;
+        public IBindableList<Channel> JoinedChannels => joinedChannels;
 
         /// <summary>
         /// The channels available for the player to join
         /// </summary>
-        public IBindableCollection<Channel> AvailableChannels => availableChannels;
+        public IBindableList<Channel> AvailableChannels => availableChannels;
 
         private IAPIProvider api;
 
diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs
index 4155121bdf..63b5b95b9c 100644
--- a/osu.Game/Online/Multiplayer/PlaylistItem.cs
+++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs
@@ -37,10 +37,10 @@ namespace osu.Game.Online.Multiplayer
         public RulesetInfo Ruleset { get; set; }
 
         [JsonIgnore]
-        public readonly BindableCollection<Mod> AllowedMods = new BindableCollection<Mod>();
+        public readonly BindableList<Mod> AllowedMods = new BindableList<Mod>();
 
         [JsonIgnore]
-        public readonly BindableCollection<Mod> RequiredMods = new BindableCollection<Mod>();
+        public readonly BindableList<Mod> RequiredMods = new BindableList<Mod>();
 
         [JsonProperty("beatmap")]
         private APIBeatmap apiBeatmap { get; set; }
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
index 448f5ced91..5273c7acfb 100644
--- a/osu.Game/Online/Multiplayer/Room.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Online.Multiplayer
         public Bindable<User> Host { get; private set; } = new Bindable<User>();
 
         [JsonProperty("playlist")]
-        public BindableCollection<PlaylistItem> Playlist { get; set; } = new BindableCollection<PlaylistItem>();
+        public BindableList<PlaylistItem> Playlist { get; set; } = new BindableList<PlaylistItem>();
 
         [JsonProperty("channel_id")]
         public Bindable<int> ChannelId { get; private set; } = new Bindable<int>();
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 685244e06b..ca9a527fad 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -16,9 +16,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
     {
         protected override string Header => "Layout";
 
-        private FillFlowContainer letterboxSettings;
-
-        private Bindable<bool> letterboxing;
         private Bindable<Size> sizeFullscreen;
 
         private OsuGameBase game;
@@ -32,7 +29,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
         {
             this.game = game;
 
-            letterboxing = config.GetBindable<bool>(FrameworkSetting.Letterboxing);
             sizeFullscreen = config.GetBindable<Size>(FrameworkSetting.SizeFullscreen);
 
             Container resolutionSettingsContainer;
@@ -49,36 +45,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y
                 },
-                new SettingsCheckbox
-                {
-                    LabelText = "Letterboxing",
-                    Bindable = letterboxing,
-                },
-                letterboxSettings = new FillFlowContainer
-                {
-                    Direction = FillDirection.Vertical,
-                    RelativeSizeAxes = Axes.X,
-                    AutoSizeAxes = Axes.Y,
-                    AutoSizeDuration = transition_duration,
-                    AutoSizeEasing = Easing.OutQuint,
-                    Masking = true,
-
-                    Children = new Drawable[]
-                    {
-                        new SettingsSlider<double>
-                        {
-                            LabelText = "Horizontal position",
-                            Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionX),
-                            KeyboardStep = 0.01f
-                        },
-                        new SettingsSlider<double>
-                        {
-                            LabelText = "Vertical position",
-                            Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionY),
-                            KeyboardStep = 0.01f
-                        },
-                    }
-                },
             };
 
             var resolutions = getResolutions();
@@ -104,15 +70,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                         resolutionDropdown.Hide();
                 }, true);
             }
-
-            letterboxing.BindValueChanged(isVisible =>
-            {
-                letterboxSettings.ClearTransforms();
-                letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None;
-
-                if (!isVisible)
-                    letterboxSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
-            }, true);
         }
 
         private IReadOnlyList<Size> getResolutions()
diff --git a/osu.Game/Screens/Multi/IRoomManager.cs b/osu.Game/Screens/Multi/IRoomManager.cs
index f0dbcb0e71..6af8a35208 100644
--- a/osu.Game/Screens/Multi/IRoomManager.cs
+++ b/osu.Game/Screens/Multi/IRoomManager.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Screens.Multi
         /// <summary>
         /// All the active <see cref="Room"/>s.
         /// </summary>
-        IBindableCollection<Room> Rooms { get; }
+        IBindableList<Room> Rooms { get; }
 
         /// <summary>
         /// Creates a new <see cref="Room"/>.
diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
index 5133e96a52..4ad8154090 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
         private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
         public IBindable<Room> SelectedRoom => selectedRoom;
 
-        private readonly IBindableCollection<Room> rooms = new BindableCollection<Room>();
+        private readonly IBindableList<Room> rooms = new BindableList<Room>();
 
         private readonly FillFlowContainer<DrawableRoom> roomFlow;
         public IReadOnlyList<DrawableRoom> Rooms => roomFlow;
diff --git a/osu.Game/Screens/Multi/RoomBindings.cs b/osu.Game/Screens/Multi/RoomBindings.cs
index dc2547268d..cdbb6dbea6 100644
--- a/osu.Game/Screens/Multi/RoomBindings.cs
+++ b/osu.Game/Screens/Multi/RoomBindings.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Screens.Multi
         public readonly Bindable<User> Host = new Bindable<User>();
         public readonly Bindable<RoomStatus> Status = new Bindable<RoomStatus>();
         public readonly Bindable<GameType> Type = new Bindable<GameType>();
-        public readonly BindableCollection<PlaylistItem> Playlist = new BindableCollection<PlaylistItem>();
+        public readonly BindableList<PlaylistItem> Playlist = new BindableList<PlaylistItem>();
         public readonly Bindable<IEnumerable<User>> Participants = new Bindable<IEnumerable<User>>();
         public readonly Bindable<int> ParticipantCount = new Bindable<int>();
         public readonly Bindable<int?> MaxParticipants = new Bindable<int?>();
diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs
index fab19c3fd7..1f95401905 100644
--- a/osu.Game/Screens/Multi/RoomManager.cs
+++ b/osu.Game/Screens/Multi/RoomManager.cs
@@ -21,8 +21,8 @@ namespace osu.Game.Screens.Multi
     {
         public event Action RoomsUpdated;
 
-        private readonly BindableCollection<Room> rooms = new BindableCollection<Room>();
-        public IBindableCollection<Room> Rooms => rooms;
+        private readonly BindableList<Room> rooms = new BindableList<Room>();
+        public IBindableList<Room> Rooms => rooms;
 
         private Room currentRoom;
 
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 103c7c20d6..d6dbb6f11c 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -18,7 +18,7 @@
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.0" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
-    <PackageReference Include="ppy.osu.Framework" Version="2018.1226.0" />
+    <PackageReference Include="ppy.osu.Framework" Version="2019.107.0" />
     <PackageReference Include="SharpCompress" Version="0.22.0" />
     <PackageReference Include="NUnit" Version="3.11.0" />
     <PackageReference Include="SharpRaven" Version="2.4.0" />

From d72412d24d6b68c137cdd247f553e07ae3eab4cd Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 7 Jan 2019 19:28:46 +0900
Subject: [PATCH 19/43] Fix leaderboard not correctly handling cancellation

---
 osu.Game/Online/Leaderboards/Leaderboard.cs | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index f3bf16a05f..2aeb503224 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
 using osu.Framework.Allocation;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
@@ -30,6 +31,7 @@ namespace osu.Game.Online.Leaderboards
         private readonly LoadingAnimation loading;
 
         private ScheduledDelegate showScoresDelegate;
+        private CancellationTokenSource showScoresCancellationSource;
 
         private bool scoresLoadedOnce;
 
@@ -60,6 +62,8 @@ namespace osu.Game.Online.Leaderboards
 
                 // schedule because we may not be loaded yet (LoadComponentAsync complains).
                 showScoresDelegate?.Cancel();
+                showScoresCancellationSource?.Cancel();
+
                 if (!IsLoaded)
                     showScoresDelegate = Schedule(showScores);
                 else
@@ -77,7 +81,7 @@ namespace osu.Game.Online.Leaderboards
                     }
 
                     scrollContainer.ScrollTo(0f, false);
-                });
+                }, (showScoresCancellationSource = new CancellationTokenSource()).Token);
             }
         }
 

From 8eedef3e8638fa9a4134c5a423f5e6025773ed22 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 7 Jan 2019 19:31:05 +0900
Subject: [PATCH 20/43] Move cancellation to safer place

---
 osu.Game/Online/Leaderboards/Leaderboard.cs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index 2aeb503224..a6748fa983 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -51,6 +51,10 @@ namespace osu.Game.Online.Leaderboards
 
                 loading.Hide();
 
+                // schedule because we may not be loaded yet (LoadComponentAsync complains).
+                showScoresDelegate?.Cancel();
+                showScoresCancellationSource?.Cancel();
+
                 if (scores == null || !scores.Any())
                     return;
 
@@ -60,10 +64,6 @@ namespace osu.Game.Online.Leaderboards
                 scrollFlow = CreateScoreFlow();
                 scrollFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1));
 
-                // schedule because we may not be loaded yet (LoadComponentAsync complains).
-                showScoresDelegate?.Cancel();
-                showScoresCancellationSource?.Cancel();
-
                 if (!IsLoaded)
                     showScoresDelegate = Schedule(showScores);
                 else

From dd960e6a89da0cf69f728911035e74790b6cd500 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 8 Jan 2019 12:50:42 +0900
Subject: [PATCH 21/43] Remove unused variable

---
 osu.Game/Graphics/Containers/ScalingContainer.cs | 2 --
 1 file changed, 2 deletions(-)

diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index 4dc25ae3d1..6cfbc5d9d2 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -16,8 +16,6 @@ namespace osu.Game.Graphics.Containers
     /// </summary>
     public class ScalingContainer : Container
     {
-        private readonly bool isTopLevel;
-
         private Bindable<float> sizeX;
         private Bindable<float> sizeY;
         private Bindable<float> posX;

From 440f4703cb5612cc3d07bfc4535cc4dcd3021011 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 8 Jan 2019 12:57:31 +0900
Subject: [PATCH 22/43] Fix toolbar offset not being applied

---
 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 c004bc6000..bb356ce7f0 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -692,7 +692,7 @@ namespace osu.Game
             ruleset.Disabled = applyBeatmapRulesetRestrictions;
             Beatmap.Disabled = applyBeatmapRulesetRestrictions;
 
-            mainContent.Padding = new MarginPadding { Top = ToolbarOffset };
+            screenContainer.Padding = new MarginPadding { Top = ToolbarOffset };
 
             MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
         }

From 199b614eba541210a13e45cc4359616345b891b8 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 8 Jan 2019 13:36:07 +0900
Subject: [PATCH 23/43] Fix masking being unapplied incorrectly

---
 osu.Game/Graphics/Containers/ScalingContainer.cs | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index 6cfbc5d9d2..ff7a1cdacf 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -45,7 +45,6 @@ namespace osu.Game.Graphics.Containers
             {
                 RelativeSizeAxes = Axes.Both,
                 RelativePositionAxes = Axes.Both,
-                Masking = true,
                 CornerRadius = 10,
                 Child = content = new DrawSizePreservingFillContainer()
             };
@@ -109,13 +108,13 @@ namespace osu.Game.Graphics.Containers
 
             var targetSize = scaling ? new Vector2(sizeX, sizeY) : Vector2.One;
             var targetPosition = scaling ? new Vector2(posX, posY) * (Vector2.One - targetSize) : Vector2.Zero;
-            bool requiresMasking = targetSize != Vector2.One;
+            bool requiresMasking = scaling && targetSize != Vector2.One;
 
             if (requiresMasking)
                 sizableContainer.Masking = true;
 
             sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart);
-            sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { content.Masking = requiresMasking; });
+            sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
         }
     }
 }

From 01aa4c2a72a1d7b43a62953482ff6faede1c4842 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 8 Jan 2019 13:48:38 +0900
Subject: [PATCH 24/43] Use TransferOnCommit

---
 .../Sections/Graphics/LayoutSettings.cs       | 36 +++++++++----------
 osu.Game/Overlays/Settings/SettingsItem.cs    |  2 +-
 2 files changed, 17 insertions(+), 21 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 9a55e97452..b6c5c4d1e6 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -7,9 +7,11 @@ using System.Drawing;
 using System.Linq;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
+using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
 using osu.Framework.Threading;
 using osu.Game.Configuration;
 using osu.Game.Graphics.Containers;
@@ -23,7 +25,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
     {
         protected override string Header => "Layout";
 
-        private FillFlowContainer scalingSettings;
+        private FillFlowContainer<SettingsSlider<float>> scalingSettings;
 
         private Bindable<ScalingMode> scalingMode;
         private Bindable<Size> sizeFullscreen;
@@ -70,7 +72,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     LabelText = "Scaling",
                     Bindable = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
                 },
-                scalingSettings = new FillFlowContainer
+                scalingSettings = new FillFlowContainer<SettingsSlider<float>>
                 {
                     Direction = FillDirection.Vertical,
                     RelativeSizeAxes = Axes.X,
@@ -78,36 +80,38 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     AutoSizeDuration = transition_duration,
                     AutoSizeEasing = Easing.OutQuint,
                     Masking = true,
-                    Children = new Drawable[]
+                    Children = new []
                     {
                         new SettingsSlider<float>
                         {
                             LabelText = "Horizontal position",
-                            Bindable = delayedBindable(scalingPositionX),
+                            Bindable = scalingPositionX,
                             KeyboardStep = 0.01f
                         },
                         new SettingsSlider<float>
                         {
                             LabelText = "Vertical position",
-                            Bindable = delayedBindable(scalingPositionY),
+                            Bindable = scalingPositionY,
                             KeyboardStep = 0.01f
                         },
                         new SettingsSlider<float>
                         {
                             LabelText = "Horizontal scale",
-                            Bindable = delayedBindable(scalingSizeX),
+                            Bindable = scalingSizeX,
                             KeyboardStep = 0.01f
                         },
                         new SettingsSlider<float>
                         {
                             LabelText = "Vertical scale",
-                            Bindable = delayedBindable(scalingSizeY),
+                            Bindable = scalingSizeY,
                             KeyboardStep = 0.01f
                         },
                     }
                 },
             };
 
+            scalingSettings.ForEach(s => bindPreviewEvent(s.Bindable));
+
             var resolutions = getResolutions();
 
             if (resolutions.Count > 1)
@@ -139,35 +143,27 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
 
                 if (mode == ScalingMode.Off)
                     scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
+
+                scalingSettings.ForEach(s => ((SliderBar<float>)s.Control).TransferValueOnCommit = mode == ScalingMode.Everything);
             }, true);
         }
 
         /// <summary>
         /// Create a delayed bindable which only updates when a condition is met.
         /// </summary>
-        /// <param name="configBindable">The config bindable.</param>
+        /// <param name="bindable">The config bindable.</param>
         /// <returns>A bindable which will propagate updates with a delay.</returns>
-        private Bindable<float> delayedBindable(Bindable<float> configBindable)
+        private void bindPreviewEvent(Bindable<float> bindable)
         {
-            var delayed = new BindableFloat { MinValue = 0, MaxValue = 1, Default = configBindable.Default };
-
-            configBindable.BindValueChanged(v => delayed.Value = v, true);
-            delayed.ValueChanged += v =>
+            bindable.ValueChanged += v =>
             {
                 switch (scalingMode.Value)
                 {
-                    case ScalingMode.Everything:
-                        applyWithDelay(() => configBindable.Value = v);
-                        return;
                     case ScalingMode.Gameplay:
                         showPreview();
                         break;
                 }
-
-                configBindable.Value = v;
             };
-
-            return delayed;
         }
 
         private Drawable preview;
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index 1d7e6350ae..5ba682c7dd 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings
     {
         protected abstract Drawable CreateControl();
 
-        protected Drawable Control { get; }
+        public Drawable Control { get; }
 
         private IHasCurrentValue<T> controlWithCurrent => Control as IHasCurrentValue<T>;
 

From a2a7aa708fa837128168aeb7ceb3d852bf1a633c Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 8 Jan 2019 14:56:42 +0900
Subject: [PATCH 25/43] Use better logic for setting slider bar settings

---
 .../Sections/Graphics/LayoutSettings.cs         |  3 +--
 osu.Game/Overlays/Settings/SettingsItem.cs      |  2 +-
 osu.Game/Overlays/Settings/SettingsSlider.cs    | 17 +++++++++--------
 3 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index b6c5c4d1e6..7f74719890 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -11,7 +11,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.UserInterface;
 using osu.Framework.Threading;
 using osu.Game.Configuration;
 using osu.Game.Graphics.Containers;
@@ -144,7 +143,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                 if (mode == ScalingMode.Off)
                     scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
 
-                scalingSettings.ForEach(s => ((SliderBar<float>)s.Control).TransferValueOnCommit = mode == ScalingMode.Everything);
+                scalingSettings.ForEach(s => s.TransferValueOnCommit = mode == ScalingMode.Everything);
             }, true);
         }
 
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index 5ba682c7dd..1d7e6350ae 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings
     {
         protected abstract Drawable CreateControl();
 
-        public Drawable Control { get; }
+        protected Drawable Control { get; }
 
         private IHasCurrentValue<T> controlWithCurrent => Control as IHasCurrentValue<T>;
 
diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs
index a3698c36e6..39a974dd2e 100644
--- a/osu.Game/Overlays/Settings/SettingsSlider.cs
+++ b/osu.Game/Overlays/Settings/SettingsSlider.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System;
-using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Game.Graphics.UserInterface;
 
@@ -23,14 +22,16 @@ namespace osu.Game.Overlays.Settings
             RelativeSizeAxes = Axes.X
         };
 
-        public float KeyboardStep;
-
-        [BackgroundDependencyLoader]
-        private void load()
+        public bool TransferValueOnCommit
         {
-            var slider = Control as U;
-            if (slider != null)
-                slider.KeyboardStep = KeyboardStep;
+            get => ((U)Control).TransferValueOnCommit;
+            set => ((U)Control).TransferValueOnCommit = value;
+        }
+
+        public float KeyboardStep
+        {
+            get => ((U)Control).KeyboardStep;
+            set => ((U)Control).KeyboardStep = value;
         }
     }
 }

From 8692be9de3d1ebebb38d2bae841ce5a21d65de0b Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 16:07:54 +0900
Subject: [PATCH 26/43] Fix sliderbar not working correctly with
 TransferValueOnCommit = true

---
 .../Graphics/UserInterface/OsuSliderBar.cs    | 87 +++++++++----------
 .../Graphics/UserInterface/ProgressBar.cs     |  2 +-
 .../Compose/Components/BeatDivisorControl.cs  |  6 +-
 osu.Game/Screens/Play/SongProgressBar.cs      |  2 +-
 4 files changed, 45 insertions(+), 52 deletions(-)

diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
index a59abcbcee..2bd84ab2b4 100644
--- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
+++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
@@ -8,7 +8,6 @@ using osuTK.Graphics;
 using osu.Framework.Allocation;
 using osu.Framework.Audio;
 using osu.Framework.Audio.Sample;
-using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.UserInterface;
 using osu.Framework.Graphics.Cursor;
@@ -33,38 +32,7 @@ namespace osu.Game.Graphics.UserInterface
         private readonly Box leftBox;
         private readonly Box rightBox;
 
-        public virtual string TooltipText
-        {
-            get
-            {
-                var bindableDouble = CurrentNumber as BindableNumber<double>;
-                var bindableFloat = CurrentNumber as BindableNumber<float>;
-                var floatValue = bindableDouble?.Value ?? bindableFloat?.Value;
-                var floatPrecision = bindableDouble?.Precision ?? bindableFloat?.Precision;
-
-                if (floatValue != null)
-                {
-                    var floatMinValue = bindableDouble?.MinValue ?? bindableFloat.MinValue;
-                    var floatMaxValue = bindableDouble?.MaxValue ?? bindableFloat.MaxValue;
-
-                    if (floatMaxValue == 1 && (floatMinValue == 0 || floatMinValue == -1))
-                        return floatValue.Value.ToString("P0");
-
-                    var decimalPrecision = normalise((decimal)floatPrecision, max_decimal_digits);
-
-                    // Find the number of significant digits (we could have less than 5 after normalize())
-                    var significantDigits = findPrecision(decimalPrecision);
-
-                    return floatValue.Value.ToString($"N{significantDigits}");
-                }
-
-                var bindableInt = CurrentNumber as BindableNumber<int>;
-                if (bindableInt != null)
-                    return bindableInt.Value.ToString("N0");
-
-                return Current.Value.ToString(CultureInfo.InvariantCulture);
-            }
-        }
+        public virtual string TooltipText { get; private set; }
 
         private Color4 accentColour;
         public Color4 AccentColour
@@ -136,21 +104,34 @@ namespace osu.Game.Graphics.UserInterface
             base.OnHoverLost(e);
         }
 
-        protected override void OnUserChange()
+        protected override bool OnMouseDown(MouseDownEvent e)
         {
-            base.OnUserChange();
-            playSample();
+            Nub.Current.Value = true;
+            return base.OnMouseDown(e);
         }
 
-        private void playSample()
+        protected override bool OnMouseUp(MouseUpEvent e)
+        {
+            Nub.Current.Value = false;
+            return base.OnMouseUp(e);
+        }
+
+        protected override void OnUserChange(T value)
+        {
+            base.OnUserChange(value);
+            playSample(value);
+            updateTooltipText(value);
+        }
+
+        private void playSample(T value)
         {
             if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50)
                 return;
 
-            if (Current.Value.Equals(lastSampleValue))
+            if (value.Equals(lastSampleValue))
                 return;
 
-            lastSampleValue = Current.Value;
+            lastSampleValue = value;
 
             lastSampleTime = Clock.CurrentTime;
             sample.Frequency.Value = 1 + NormalizedValue * 0.2f;
@@ -163,16 +144,28 @@ namespace osu.Game.Graphics.UserInterface
             sample.Play();
         }
 
-        protected override bool OnMouseDown(MouseDownEvent e)
+        private void updateTooltipText(T value)
         {
-            Nub.Current.Value = true;
-            return base.OnMouseDown(e);
-        }
+            if (CurrentNumber.IsInteger)
+                TooltipText = ((int)Convert.ChangeType(value, typeof(int))).ToString("N0");
+            else
+            {
+                double floatValue = (double)Convert.ChangeType(value, typeof(double));
+                double floatMinValue = (double)Convert.ChangeType(CurrentNumber.MinValue, typeof(double));
+                double floatMaxValue = (double)Convert.ChangeType(CurrentNumber.MaxValue, typeof(double));
 
-        protected override bool OnMouseUp(MouseUpEvent e)
-        {
-            Nub.Current.Value = false;
-            return base.OnMouseUp(e);
+                if (floatMaxValue == 1 && floatMinValue >= -1)
+                    TooltipText = floatValue.ToString("P0");
+                else
+                {
+                    var decimalPrecision = normalise((decimal)Convert.ChangeType(CurrentNumber.Precision, typeof(decimal)), max_decimal_digits);
+
+                    // Find the number of significant digits (we could have less than 5 after normalize())
+                    var significantDigits = findPrecision(decimalPrecision);
+
+                    TooltipText = floatValue.ToString($"N{significantDigits}");
+                }
+            }
         }
 
         protected override void UpdateAfterChildren()
diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs
index ee64c7c25c..d03b9b30dc 100644
--- a/osu.Game/Graphics/UserInterface/ProgressBar.cs
+++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs
@@ -62,6 +62,6 @@ namespace osu.Game.Graphics.UserInterface
             fill.Width = value * UsableWidth;
         }
 
-        protected override void OnUserChange() => OnSeek?.Invoke(Current);
+        protected override void OnUserChange(double value) => OnSeek?.Invoke(Current);
     }
 }
diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
index aa63b02013..373f4d1682 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
@@ -238,11 +238,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
                 {
                     case Key.Right:
                         beatDivisor.Next();
-                        OnUserChange();
+                        OnUserChange(Current);
                         return true;
                     case Key.Left:
                         beatDivisor.Previous();
-                        OnUserChange();
+                        OnUserChange(Current);
                         return true;
                     default:
                         return false;
@@ -279,7 +279,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
                 var xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth;
 
                 CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First();
-                OnUserChange();
+                OnUserChange(Current);
             }
 
             private float getMappedPosition(float divisor) => (float)Math.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f);
diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs
index 1f0c4936a5..00206e05ff 100644
--- a/osu.Game/Screens/Play/SongProgressBar.cs
+++ b/osu.Game/Screens/Play/SongProgressBar.cs
@@ -112,6 +112,6 @@ namespace osu.Game.Screens.Play
             handleBase.X = xFill;
         }
 
-        protected override void OnUserChange() => OnSeek?.Invoke(Current);
+        protected override void OnUserChange(double value) => OnSeek?.Invoke(Current);
     }
 }

From 38a3ccc817de72c87c665b9d9760c743d112f178 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 16:31:44 +0900
Subject: [PATCH 27/43] Use value where applicable

---
 osu.Game/Graphics/UserInterface/ProgressBar.cs | 2 +-
 osu.Game/Screens/Play/SongProgressBar.cs       | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs
index d03b9b30dc..6dca58f70d 100644
--- a/osu.Game/Graphics/UserInterface/ProgressBar.cs
+++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs
@@ -62,6 +62,6 @@ namespace osu.Game.Graphics.UserInterface
             fill.Width = value * UsableWidth;
         }
 
-        protected override void OnUserChange(double value) => OnSeek?.Invoke(Current);
+        protected override void OnUserChange(double value) => OnSeek?.Invoke(value);
     }
 }
diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs
index 00206e05ff..b06a34e603 100644
--- a/osu.Game/Screens/Play/SongProgressBar.cs
+++ b/osu.Game/Screens/Play/SongProgressBar.cs
@@ -112,6 +112,6 @@ namespace osu.Game.Screens.Play
             handleBase.X = xFill;
         }
 
-        protected override void OnUserChange(double value) => OnSeek?.Invoke(Current);
+        protected override void OnUserChange(double value) => OnSeek?.Invoke(value);
     }
 }

From daeba63242302f47e3787cbbbbf917128437b97d Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 16:37:18 +0900
Subject: [PATCH 28/43] Remove more unused code

---
 .../Settings/Sections/Graphics/LayoutSettings.cs   | 14 --------------
 1 file changed, 14 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 7f74719890..22fcf8a6a4 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -175,20 +175,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
             preview.Expire();
         }
 
-        private ScheduledDelegate delayedApplication;
-
-        private void applyWithDelay(Action func, bool firstRun = true)
-        {
-            if (!firstRun && !GetContainingInputManager().CurrentState.Mouse.IsPressed(MouseButton.Left))
-            {
-                func();
-                return;
-            }
-
-            delayedApplication?.Cancel();
-            delayedApplication = Scheduler.AddDelayed(() => applyWithDelay(func, false), 250);
-        }
-
         private IReadOnlyList<Size> getResolutions()
         {
             var resolutions = new List<Size> { new Size(9999, 9999) };

From 2c44b928d37d4fd4d4ac49d507d11c36c74c954e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 16:38:34 +0900
Subject: [PATCH 29/43] Remove unused references

---
 osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 22fcf8a6a4..3fa4276616 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -1,7 +1,6 @@
 // Copyright (c) 2007-2018 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.Drawing;
 using System.Linq;
@@ -11,12 +10,10 @@ using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
-using osu.Framework.Threading;
 using osu.Game.Configuration;
 using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.UserInterface;
 using osuTK.Graphics;
-using osuTK.Input;
 
 namespace osu.Game.Overlays.Settings.Sections.Graphics
 {

From cf8bcb7ba29ebfdd38a242735bfe21c6ceb7e2e9 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 17:53:43 +0900
Subject: [PATCH 30/43] Add explicit beatmap -> scores relationship rather than
 relying on cascades

---
 osu.Game/Beatmaps/BeatmapInfo.cs              |  7 ++++
 osu.Game/Beatmaps/BeatmapStore.cs             |  3 +-
 .../Migrations/OsuDbContextModelSnapshot.cs   | 42 +++++++++----------
 3 files changed, 30 insertions(+), 22 deletions(-)

diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 0534fd9253..6ad5b2070e 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System;
+using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.Linq;
@@ -9,6 +10,7 @@ using Newtonsoft.Json;
 using osu.Game.Database;
 using osu.Game.IO.Serialization;
 using osu.Game.Rulesets;
+using osu.Game.Scoring;
 
 namespace osu.Game.Beatmaps
 {
@@ -112,6 +114,11 @@ namespace osu.Game.Beatmaps
         [JsonProperty("difficulty_rating")]
         public double StarDifficulty { get; set; }
 
+        /// <summary>
+        /// Currently only populated for beatmap deletion. Use <see cref="ScoreManager"/> to query scores.
+        /// </summary>
+        public List<ScoreInfo> Scores { get; set; }
+
         public override string ToString() => $"{Metadata} [{Version}]";
 
         public bool Equals(BeatmapInfo other)
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index 5bdc42cdf3..6817c0653d 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -64,7 +64,8 @@ namespace osu.Game.Beatmaps
             base.AddIncludesForDeletion(query)
                 .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
                 .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
-                .Include(s => s.Metadata);
+                .Include(s => s.Metadata)
+                .Include(s => s.Beatmaps).ThenInclude(b => b.Scores);
 
         protected override IQueryable<BeatmapSetInfo> AddIncludesForConsumption(IQueryable<BeatmapSetInfo> query) =>
             base.AddIncludesForConsumption(query)
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index 8026847e3b..2dafedc3ac 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Migrations
         {
 #pragma warning disable 612, 618
             modelBuilder
-                .HasAnnotation("ProductVersion", "2.1.4-rtm-31024");
+                .HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
 
             modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
                 {
@@ -215,6 +215,25 @@ namespace osu.Game.Migrations
                     b.ToTable("Settings");
                 });
 
+            modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Hash");
+
+                    b.Property<int>("ReferenceCount");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Hash")
+                        .IsUnique();
+
+                    b.HasIndex("ReferenceCount");
+
+                    b.ToTable("FileInfo");
+                });
+
             modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
                 {
                     b.Property<int>("ID")
@@ -239,25 +258,6 @@ namespace osu.Game.Migrations
                     b.ToTable("KeyBinding");
                 });
 
-            modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
-                {
-                    b.Property<int>("ID")
-                        .ValueGeneratedOnAdd();
-
-                    b.Property<string>("Hash");
-
-                    b.Property<int>("ReferenceCount");
-
-                    b.HasKey("ID");
-
-                    b.HasIndex("Hash")
-                        .IsUnique();
-
-                    b.HasIndex("ReferenceCount");
-
-                    b.ToTable("FileInfo");
-                });
-
             modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
                 {
                     b.Property<int?>("ID")
@@ -454,7 +454,7 @@ namespace osu.Game.Migrations
             modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
                 {
                     b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
-                        .WithMany()
+                        .WithMany("Scores")
                         .HasForeignKey("BeatmapInfoID")
                         .OnDelete(DeleteBehavior.Cascade);
 

From b17b88d071ded055c0aa65b2118692ae90fb74cf Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 18:06:46 +0900
Subject: [PATCH 31/43] Fix null beatmap possibly being selected

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

diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index f65cc0e49d..2f212a2564 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -297,14 +297,14 @@ namespace osu.Game.Screens.Select
         /// <param name="performStartAction">Whether to trigger <see cref="OnStart"/>.</param>
         public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true)
         {
-            // avoid attempting to continue before a selection has been obtained.
-            // this could happen via a user interaction while the carousel is still in a loading state.
-            if (Carousel.SelectedBeatmap == null) return;
-
             // if we have a pending filter operation, we want to run it now.
             // it could change selection (ie. if the ruleset has been changed).
             Carousel.FlushPendingFilterOperations();
 
+            // avoid attempting to continue before a selection has been obtained.
+            // this could happen via a user interaction while the carousel is still in a loading state.
+            if (Carousel.SelectedBeatmap == null) return;
+
             if (beatmap != null)
                 Carousel.SelectBeatmap(beatmap);
 

From da98915c0c4c3cf64ab647327612bc7acde43b72 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 18:58:44 +0900
Subject: [PATCH 32/43] Fix links not working in partially masked text flow

---
 osu.Game/Graphics/Containers/LinkFlowContainer.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
index 74315d2522..7646ff723e 100644
--- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs
+++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
@@ -79,6 +79,7 @@ namespace osu.Game.Graphics.Containers
         {
             AddInternal(new DrawableLinkCompiler(drawables.OfType<SpriteText>().ToList())
             {
+                RelativeSizeAxes = Axes.Both,
                 TooltipText = tooltipText ?? (url != text ? url : string.Empty),
                 Action = action ?? (() =>
                 {
@@ -122,5 +123,7 @@ namespace osu.Game.Graphics.Containers
                 }),
             });
         }
+
+        public override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Where(c => !(c is DrawableLinkCompiler));
     }
 }

From 122fc2de582d496a3f98f70f13f57b587f32cc14 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 19:24:55 +0900
Subject: [PATCH 33/43] Show room leaderboard instead in the lounge

---
 .../Visual/TestCaseMatchLeaderboard.cs        |  3 +-
 .../Multi/Lounge/Components/RoomInspector.cs  | 28 ++++++-------------
 .../Match/Components/MatchLeaderboard.cs      | 15 +++++-----
 .../Screens/Multi/Match/MatchSubScreen.cs     |  5 ++--
 .../Ranking/Pages/RoomLeaderboardPage.cs      |  2 +-
 osu.Game/Screens/Multi/RoomBindings.cs        |  3 ++
 6 files changed, 25 insertions(+), 31 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs
index cf475de1f0..821bf84047 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs
@@ -17,12 +17,13 @@ namespace osu.Game.Tests.Visual
     {
         public TestCaseMatchLeaderboard()
         {
-            Add(new MatchLeaderboard(new Room { RoomID = { Value = 3 } })
+            Add(new MatchLeaderboard
             {
                 Origin = Anchor.Centre,
                 Anchor = Anchor.Centre,
                 Size = new Vector2(550f, 450f),
                 Scope = MatchLeaderboardScope.Overall,
+                Room = new Room { RoomID = { Value = 3 } }
             });
         }
 
diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
index 47f5182c39..ef80499884 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
@@ -1,7 +1,7 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using System.Linq;
+using System.Threading;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;
@@ -13,10 +13,10 @@ using osu.Framework.Graphics.Shapes;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Drawables;
 using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Screens.Multi.Components;
+using osu.Game.Screens.Multi.Match.Components;
 using osu.Game.Users;
 using osuTK;
 using osuTK.Graphics;
@@ -37,11 +37,11 @@ namespace osu.Game.Screens.Multi.Lounge.Components
         private Box statusStrip;
         private UpdateableBeatmapBackgroundSprite background;
         private ParticipantCountDisplay participantCount;
-        private FillFlowContainer topFlow, participantsFlow;
+        private FillFlowContainer topFlow;
         private OsuSpriteText name, status;
         private BeatmapTypeInfo beatmapTypeInfo;
-        private ScrollContainer participantsScroll;
         private ParticipantInfo participantInfo;
+        private MatchLeaderboard leaderboard;
 
         [Resolved]
         private BeatmapManager beatmaps { get; set; }
@@ -147,23 +147,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components
                         },
                     },
                 },
-                participantsScroll = new OsuScrollContainer
+                leaderboard = new MatchLeaderboard
                 {
                     Anchor = Anchor.BottomLeft,
                     Origin = Anchor.BottomLeft,
                     RelativeSizeAxes = Axes.X,
                     Padding = new MarginPadding { Top = contentPadding.Top, Left = 38, Right = 37 },
-                    Children = new[]
-                    {
-                        participantsFlow = new FillFlowContainer
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            AutoSizeAxes = Axes.Y,
-                            LayoutDuration = transition_duration,
-                            Spacing = new Vector2(5f),
-                        },
-                    },
-                },
+                }
             };
 
             participantInfo.Host.BindTo(bindings.Host);
@@ -180,7 +170,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components
             background.Beatmap.BindTo(bindings.CurrentBeatmap);
 
             bindings.Status.BindValueChanged(displayStatus);
-            bindings.Participants.BindValueChanged(p => participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u)));
             bindings.Name.BindValueChanged(n => name.Text = n);
 
             Room.BindValueChanged(updateRoom, true);
@@ -189,10 +178,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components
         private void updateRoom(Room room)
         {
             bindings.Room = room;
+            leaderboard.Room = room;
 
             if (room != null)
             {
-                participantsFlow.FadeIn(transition_duration);
                 participantCount.FadeIn(transition_duration);
                 beatmapTypeInfo.FadeIn(transition_duration);
                 name.FadeIn(transition_duration);
@@ -200,7 +189,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components
             }
             else
             {
-                participantsFlow.FadeOut(transition_duration);
                 participantCount.FadeOut(transition_duration);
                 beatmapTypeInfo.FadeOut(transition_duration);
                 name.FadeOut(transition_duration);
@@ -214,7 +202,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
         {
             base.UpdateAfterChildren();
 
-            participantsScroll.Height = DrawHeight - topFlow.DrawHeight;
+            leaderboard.Height = DrawHeight - topFlow.DrawHeight;
         }
 
         private void displayStatus(RoomStatus s)
diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
index 864191105f..5ac0453373 100644
--- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
+++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
@@ -16,17 +16,18 @@ namespace osu.Game.Screens.Multi.Match.Components
     {
         public Action<IEnumerable<APIRoomScoreInfo>> ScoresLoaded;
 
-        private readonly Room room;
-
-        public MatchLeaderboard(Room room)
+        public Room Room
         {
-            this.room = room;
+            get => bindings.Room;
+            set => bindings.Room = value;
         }
 
+        private readonly RoomBindings bindings = new RoomBindings();
+
         [BackgroundDependencyLoader]
         private void load()
         {
-            room.RoomID.BindValueChanged(id =>
+            bindings.RoomID.BindValueChanged(id =>
             {
                 if (id == null)
                     return;
@@ -38,10 +39,10 @@ namespace osu.Game.Screens.Multi.Match.Components
 
         protected override APIRequest FetchScores(Action<IEnumerable<APIRoomScoreInfo>> scoresCallback)
         {
-            if (room.RoomID == null)
+            if (bindings.RoomID.Value == null)
                 return null;
 
-            var req = new GetRoomScoresRequest(room.RoomID.Value ?? 0);
+            var req = new GetRoomScoresRequest(bindings.RoomID.Value ?? 0);
 
             req.Success += r =>
             {
diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
index 55a5a2c85e..14cdd90128 100644
--- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
+++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
@@ -71,10 +71,11 @@ namespace osu.Game.Screens.Multi.Match
                                 {
                                     new Drawable[]
                                     {
-                                        leaderboard = new MatchLeaderboard(room)
+                                        leaderboard = new MatchLeaderboard
                                         {
                                             Padding = new MarginPadding(10),
-                                            RelativeSizeAxes = Axes.Both
+                                            RelativeSizeAxes = Axes.Both,
+                                            Room = room
                                         },
                                         new Container
                                         {
diff --git a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs
index 54528e5503..44f5f11c93 100644
--- a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs
+++ b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs
@@ -103,8 +103,8 @@ namespace osu.Game.Screens.Multi.Ranking.Pages
         public class ResultsMatchLeaderboard : MatchLeaderboard
         {
             public ResultsMatchLeaderboard(Room room)
-                : base(room)
             {
+                Room = room;
             }
 
             protected override bool FadeTop => true;
diff --git a/osu.Game/Screens/Multi/RoomBindings.cs b/osu.Game/Screens/Multi/RoomBindings.cs
index cdbb6dbea6..30e2918b69 100644
--- a/osu.Game/Screens/Multi/RoomBindings.cs
+++ b/osu.Game/Screens/Multi/RoomBindings.cs
@@ -39,6 +39,7 @@ namespace osu.Game.Screens.Multi
 
                 if (room != null)
                 {
+                    RoomID.UnbindFrom(room.RoomID);
                     Name.UnbindFrom(room.Name);
                     Host.UnbindFrom(room.Host);
                     Status.UnbindFrom(room.Status);
@@ -56,6 +57,7 @@ namespace osu.Game.Screens.Multi
 
                 if (room != null)
                 {
+                    RoomID.BindTo(room.RoomID);
                     Name.BindTo(room.Name);
                     Host.BindTo(room.Host);
                     Status.BindTo(room.Status);
@@ -82,6 +84,7 @@ namespace osu.Game.Screens.Multi
             currentRuleset.Value = playlistItem?.Ruleset;
         }
 
+        public readonly Bindable<int?> RoomID = new Bindable<int?>();
         public readonly Bindable<string> Name = new Bindable<string>();
         public readonly Bindable<User> Host = new Bindable<User>();
         public readonly Bindable<RoomStatus> Status = new Bindable<RoomStatus>();

From 2a4c91a6ab7a1d25a15ba61ad4581b48de13ab6e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 8 Jan 2019 19:26:24 +0900
Subject: [PATCH 34/43] Remove unused using

---
 osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
index ef80499884..e8be62e28c 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
@@ -1,7 +1,6 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using System.Threading;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;

From 415df52c69ec42846a26f1e9d43ffc8fdbaa2edf Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 8 Jan 2019 21:23:57 +0900
Subject: [PATCH 35/43] Update framework

---
 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 d6dbb6f11c..8f00e81237 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -18,7 +18,7 @@
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.0" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
-    <PackageReference Include="ppy.osu.Framework" Version="2019.107.0" />
+    <PackageReference Include="ppy.osu.Framework" Version="2019.108.0" />
     <PackageReference Include="SharpCompress" Version="0.22.0" />
     <PackageReference Include="NUnit" Version="3.11.0" />
     <PackageReference Include="SharpRaven" Version="2.4.0" />

From 2dc185f249bfc4766d9fad8703af999406a5331d Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 9 Jan 2019 15:15:54 +0900
Subject: [PATCH 36/43] Display avatars rather than full scores

---
 .../Multi/Lounge/Components/RoomInspector.cs  | 351 +++++++++++-------
 1 file changed, 215 insertions(+), 136 deletions(-)

diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
index e8be62e28c..63730ff635 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;
@@ -13,9 +14,10 @@ using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Drawables;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Screens.Multi.Components;
-using osu.Game.Screens.Multi.Match.Components;
 using osu.Game.Users;
 using osuTK;
 using osuTK.Graphics;
@@ -36,11 +38,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components
         private Box statusStrip;
         private UpdateableBeatmapBackgroundSprite background;
         private ParticipantCountDisplay participantCount;
-        private FillFlowContainer topFlow;
         private OsuSpriteText name, status;
         private BeatmapTypeInfo beatmapTypeInfo;
         private ParticipantInfo participantInfo;
-        private MatchLeaderboard leaderboard;
+        private MatchParticipants participants;
 
         [Resolved]
         private BeatmapManager beatmaps { get; set; }
@@ -57,127 +58,138 @@ namespace osu.Game.Screens.Multi.Lounge.Components
                     RelativeSizeAxes = Axes.Both,
                     Colour = OsuColour.FromHex(@"343138"),
                 },
-                topFlow = new FillFlowContainer
+                new GridContainer
                 {
-                    RelativeSizeAxes = Axes.X,
-                    AutoSizeAxes = Axes.Y,
-                    Direction = FillDirection.Vertical,
-                    Children = new Drawable[]
+                    RelativeSizeAxes = Axes.Both,
+                    RowDimensions = new[]
                     {
-                        new Container
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            Height = 200,
-                            Masking = true,
-                            Children = new Drawable[]
-                            {
-                                background = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both },
-                                new Box
-                                {
-                                    RelativeSizeAxes = Axes.Both,
-                                    Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)),
-                                },
-                                new Container
-                                {
-                                    RelativeSizeAxes = Axes.Both,
-                                    Padding = new MarginPadding(20),
-                                    Children = new Drawable[]
-                                    {
-                                        participantCount = new ParticipantCountDisplay
-                                        {
-                                            Anchor = Anchor.TopRight,
-                                            Origin = Anchor.TopRight,
-                                        },
-                                        name = new OsuSpriteText
-                                        {
-                                            Anchor = Anchor.BottomLeft,
-                                            Origin = Anchor.BottomLeft,
-                                            TextSize = 30,
-                                        },
-                                    },
-                                },
-                            },
-                        },
-                        statusStrip = new Box
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            Height = 5,
-                        },
-                        new Container
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            AutoSizeAxes = Axes.Y,
-                            Children = new Drawable[]
-                            {
-                                new Box
-                                {
-                                    RelativeSizeAxes = Axes.Both,
-                                    Colour = OsuColour.FromHex(@"28242d"),
-                                },
-                                new FillFlowContainer
-                                {
-                                    RelativeSizeAxes = Axes.X,
-                                    AutoSizeAxes = Axes.Y,
-                                    Direction = FillDirection.Vertical,
-                                    LayoutDuration = transition_duration,
-                                    Padding = contentPadding,
-                                    Spacing = new Vector2(0f, 5f),
-                                    Children = new Drawable[]
-                                    {
-                                        status = new OsuSpriteText
-                                        {
-                                            TextSize = 14,
-                                            Font = @"Exo2.0-Bold",
-                                        },
-                                        beatmapTypeInfo = new BeatmapTypeInfo(),
-                                    },
-                                },
-                            },
-                        },
-                        new Container
-                        {
-                            RelativeSizeAxes = Axes.X,
-                            AutoSizeAxes = Axes.Y,
-                            Padding = contentPadding,
-                            Children = new Drawable[]
-                            {
-                                participantInfo = new ParticipantInfo(),
-                            },
-                        },
+                        new Dimension(GridSizeMode.AutoSize),
+                        new Dimension(GridSizeMode.Distributed),
                     },
-                },
-                leaderboard = new MatchLeaderboard
-                {
-                    Anchor = Anchor.BottomLeft,
-                    Origin = Anchor.BottomLeft,
-                    RelativeSizeAxes = Axes.X,
-                    Padding = new MarginPadding { Top = contentPadding.Top, Left = 38, Right = 37 },
+                    Content = new[]
+                    {
+                        new Drawable[]
+                        {
+                            new FillFlowContainer
+                            {
+                                RelativeSizeAxes = Axes.X,
+                                AutoSizeAxes = Axes.Y,
+                                Direction = FillDirection.Vertical,
+                                Children = new Drawable[]
+                                {
+                                    new Container
+                                    {
+                                        RelativeSizeAxes = Axes.X,
+                                        Height = 200,
+                                        Masking = true,
+                                        Children = new Drawable[]
+                                        {
+                                            background = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both },
+                                            new Box
+                                            {
+                                                RelativeSizeAxes = Axes.Both,
+                                                Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)),
+                                            },
+                                            new Container
+                                            {
+                                                RelativeSizeAxes = Axes.Both,
+                                                Padding = new MarginPadding(20),
+                                                Children = new Drawable[]
+                                                {
+                                                    participantCount = new ParticipantCountDisplay
+                                                    {
+                                                        Anchor = Anchor.TopRight,
+                                                        Origin = Anchor.TopRight,
+                                                    },
+                                                    name = new OsuSpriteText
+                                                    {
+                                                        Anchor = Anchor.BottomLeft,
+                                                        Origin = Anchor.BottomLeft,
+                                                        TextSize = 30,
+                                                    },
+                                                },
+                                            },
+                                        },
+                                    },
+                                    statusStrip = new Box
+                                    {
+                                        RelativeSizeAxes = Axes.X,
+                                        Height = 5,
+                                    },
+                                    new Container
+                                    {
+                                        RelativeSizeAxes = Axes.X,
+                                        AutoSizeAxes = Axes.Y,
+                                        Children = new Drawable[]
+                                        {
+                                            new Box
+                                            {
+                                                RelativeSizeAxes = Axes.Both,
+                                                Colour = OsuColour.FromHex(@"28242d"),
+                                            },
+                                            new FillFlowContainer
+                                            {
+                                                RelativeSizeAxes = Axes.X,
+                                                AutoSizeAxes = Axes.Y,
+                                                Direction = FillDirection.Vertical,
+                                                LayoutDuration = transition_duration,
+                                                Padding = contentPadding,
+                                                Spacing = new Vector2(0f, 5f),
+                                                Children = new Drawable[]
+                                                {
+                                                    status = new OsuSpriteText
+                                                    {
+                                                        TextSize = 14,
+                                                        Font = @"Exo2.0-Bold",
+                                                    },
+                                                    beatmapTypeInfo = new BeatmapTypeInfo(),
+                                                },
+                                            },
+                                        },
+                                    },
+                                    new Container
+                                    {
+                                        RelativeSizeAxes = Axes.X,
+                                        AutoSizeAxes = Axes.Y,
+                                        Padding = contentPadding,
+                                        Children = new Drawable[]
+                                        {
+                                            participantInfo = new ParticipantInfo(),
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                        new Drawable[]
+                        {
+                            participants = new MatchParticipants
+                            {
+                                RelativeSizeAxes = Axes.Both,
+                            }
+                        }
+                    }
                 }
             };
 
             participantInfo.Host.BindTo(bindings.Host);
             participantInfo.ParticipantCount.BindTo(bindings.ParticipantCount);
             participantInfo.Participants.BindTo(bindings.Participants);
-
             participantCount.Participants.BindTo(bindings.Participants);
             participantCount.ParticipantCount.BindTo(bindings.ParticipantCount);
             participantCount.MaxParticipants.BindTo(bindings.MaxParticipants);
-
             beatmapTypeInfo.Beatmap.BindTo(bindings.CurrentBeatmap);
             beatmapTypeInfo.Ruleset.BindTo(bindings.CurrentRuleset);
             beatmapTypeInfo.Type.BindTo(bindings.Type);
             background.Beatmap.BindTo(bindings.CurrentBeatmap);
-
             bindings.Status.BindValueChanged(displayStatus);
             bindings.Name.BindValueChanged(n => name.Text = n);
-
             Room.BindValueChanged(updateRoom, true);
         }
 
         private void updateRoom(Room room)
         {
             bindings.Room = room;
-            leaderboard.Room = room;
+            participants.Room = room;
 
             if (room != null)
             {
@@ -197,13 +209,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components
             }
         }
 
-        protected override void UpdateAfterChildren()
-        {
-            base.UpdateAfterChildren();
-
-            leaderboard.Height = DrawHeight - topFlow.DrawHeight;
-        }
-
         private void displayStatus(RoomStatus s)
         {
             status.Text = s.Message;
@@ -213,39 +218,113 @@ namespace osu.Game.Screens.Multi.Lounge.Components
             status.FadeColour(c, transition_duration);
         }
 
-        private class UserTile : Container, IHasTooltip
-        {
-            private readonly User user;
-
-            public string TooltipText => user.Username;
-
-            public UserTile(User user)
-            {
-                this.user = user;
-                Size = new Vector2(70f);
-                CornerRadius = 5f;
-                Masking = true;
-
-                Children = new Drawable[]
-                {
-                    new Box
-                    {
-                        RelativeSizeAxes = Axes.Both,
-                        Colour = OsuColour.FromHex(@"27252d"),
-                    },
-                    new UpdateableAvatar
-                    {
-                        RelativeSizeAxes = Axes.Both,
-                        User = user,
-                    },
-                };
-            }
-        }
-
         private class RoomStatusNoneSelected : RoomStatus
         {
             public override string Message => @"No Room Selected";
             public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8;
         }
+
+        private class MatchParticipants : CompositeDrawable
+        {
+            private Room room;
+            private readonly FillFlowContainer fill;
+
+            public Room Room
+            {
+                get { return room; }
+                set
+                {
+                    if (room == value)
+                        return;
+
+                    room = value;
+                    updateParticipants();
+                }
+            }
+
+            public MatchParticipants()
+            {
+                Padding = new MarginPadding { Horizontal = 10 };
+
+                InternalChild = new ScrollContainer
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Child = fill = new FillFlowContainer
+                    {
+                        Spacing = new Vector2(10),
+                        RelativeSizeAxes = Axes.X,
+                        AutoSizeAxes = Axes.Y,
+                        Direction = FillDirection.Full,
+                    }
+                };
+            }
+
+            [Resolved]
+            private APIAccess api { get; set; }
+
+            private GetRoomScoresRequest request;
+
+            private void updateParticipants()
+            {
+                var roomId = room.RoomID.Value ?? 0;
+
+                request?.Cancel();
+
+                // nice little progressive fade
+                int time = 500;
+                foreach (var c in fill.Children)
+                {
+                    c.Delay(500 - time).FadeOut(time, Easing.Out);
+                    time = Math.Max(20, time - 20);
+                    c.Expire();
+                }
+
+                if (roomId == 0) return;
+
+                request = new GetRoomScoresRequest(roomId);
+                request.Success += scores =>
+                {
+                    if (roomId != room.RoomID.Value)
+                        return;
+
+                    fill.Clear();
+                    foreach (var s in scores)
+                        fill.Add(new UserTile(s.User));
+
+                    fill.FadeInFromZero(1000, Easing.OutQuint);
+                };
+
+                api.Queue(request);
+            }
+
+            private class UserTile : CompositeDrawable, IHasTooltip
+            {
+                private readonly User user;
+
+                public string TooltipText => user.Username;
+
+                public UserTile(User user)
+                {
+                    this.user = user;
+                    Size = new Vector2(70f);
+                    CornerRadius = 5f;
+                    Masking = true;
+
+                    InternalChildren = new Drawable[]
+                    {
+                        new Box
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Colour = OsuColour.FromHex(@"27252d"),
+                        },
+                        new UpdateableAvatar
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            User = user,
+                        },
+                    };
+                }
+            }
+        }
     }
 }

From 045ed741b0a253352f82b5a7806d942f1565dced Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 9 Jan 2019 15:29:27 +0900
Subject: [PATCH 37/43] Fix API getting stuck in eternal failing state if login
 request fails

---
 osu.Game/Online/API/APIAccess.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 10b4e73419..db273dd00a 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -101,6 +101,9 @@ namespace osu.Game.Online.API
                         //todo: replace this with a ping request.
                         log.Add(@"In a failing state, waiting a bit before we try again...");
                         Thread.Sleep(5000);
+
+                        if (!IsLoggedIn) goto case APIState.Connecting;
+
                         if (queue.Count == 0)
                         {
                             log.Add(@"Queueing a ping request");

From dfe35f850c2575c1e9a10563cac4e600755acae3 Mon Sep 17 00:00:00 2001
From: VINXIS <oykxf2@gmail.com>
Date: Wed, 9 Jan 2019 01:42:48 -0700
Subject: [PATCH 38/43] Add rebalances to lazer performance calc

---
 .../Difficulty/OsuPerformanceCalculator.cs    | 22 ++++++++++---------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 16f0af9875..5f061d0954 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -105,13 +105,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
 
             double approachRateFactor = 1.0f;
             if (Attributes.ApproachRate > 10.33f)
-                approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f);
+                approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
             else if (Attributes.ApproachRate < 8.0f)
             {
-                // HD is worth more with lower ar!
-                if (mods.Any(h => h is OsuModHidden))
-                    approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate);
-                else
                     approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
             }
 
@@ -119,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
 
             // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
             if (mods.Any(h => h is OsuModHidden))
-                aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
+                aimValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate);
 
             if (mods.Any(h => h is OsuModFlashlight))
             {
@@ -151,14 +147,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty
             // Combo scaling
             if (beatmapMaxCombo > 0)
                 speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+            
+            double approachRateFactor = 1.0f;
+            if (Attributes.ApproachRate > 10.33f)
+                approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
+
+            speedValue *= approachRateFactor;
 
             if (mods.Any(m => m is OsuModHidden))
-                speedValue *= 1.18f;
+                speedValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate);
 
             // Scale the speed value with accuracy _slightly_
-            speedValue *= 0.5f + accuracy / 2.0f;
+            speedValue *= 0.02f + accuracy;
             // It is important to also consider accuracy difficulty when doing that
-            speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
+            speedValue *= 0.96f + Math.Pow(Attributes.OverallDifficulty, 2) / 1600;
 
             return speedValue;
         }
@@ -186,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
             accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
 
             if (mods.Any(m => m is OsuModHidden))
-                accuracyValue *= 1.02f;
+                accuracyValue *= 1.08f;
             if (mods.Any(m => m is OsuModFlashlight))
                 accuracyValue *= 1.02f;
 

From a09615144ecd86876879e52841c12dcff09eb2b5 Mon Sep 17 00:00:00 2001
From: VINXIS <oykxf2@gmail.com>
Date: Wed, 9 Jan 2019 01:47:39 -0700
Subject: [PATCH 39/43] Kill White Space

---
 osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 5f061d0954..efa23f1a26 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
             // Combo scaling
             if (beatmapMaxCombo > 0)
                 speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
-            
+
             double approachRateFactor = 1.0f;
             if (Attributes.ApproachRate > 10.33f)
                 approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);

From 4f5c208672802bc3cf24a922e807e554774f9689 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 9 Jan 2019 19:01:33 +0900
Subject: [PATCH 40/43] Add UI scale

Limited to (relatively) sane values until we eventually get around to adjusting UI to allow higher extermities.
---
 osu.Game/Configuration/OsuConfigManager.cs    |  5 +++-
 .../Graphics/Containers/ScalingContainer.cs   | 29 ++++++++++++++++++-
 osu.Game/OsuGame.cs                           |  5 +++-
 .../Sections/Graphics/LayoutSettings.cs       | 15 +++++++++-
 4 files changed, 50 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 8df286ffb2..1b279eee44 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -105,6 +105,8 @@ namespace osu.Game.Configuration
 
             Set(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f);
             Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
+
+            Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
         }
 
         public OsuConfigManager(Storage storage)
@@ -167,6 +169,7 @@ namespace osu.Game.Configuration
         ScalingPositionX,
         ScalingPositionY,
         ScalingSizeX,
-        ScalingSizeY
+        ScalingSizeY,
+        UIScale
     }
 }
diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index ff7a1cdacf..8d21d6de10 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -46,10 +46,37 @@ namespace osu.Game.Graphics.Containers
                 RelativeSizeAxes = Axes.Both,
                 RelativePositionAxes = Axes.Both,
                 CornerRadius = 10,
-                Child = content = new DrawSizePreservingFillContainer()
+                Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay)
             };
         }
 
+        private class ScalingDrawSizePreservingFillContainer : DrawSizePreservingFillContainer
+        {
+            private readonly bool applyUIScale;
+            private Bindable<float> uiScale;
+
+            public ScalingDrawSizePreservingFillContainer(bool applyUIScale)
+            {
+                this.applyUIScale = applyUIScale;
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(OsuConfigManager osuConfig)
+            {
+                if (applyUIScale)
+                {
+                    uiScale = osuConfig.GetBindable<float>(OsuSetting.UIScale);
+                    uiScale.BindValueChanged(scaleChanged, true);
+                }
+            }
+
+            private void scaleChanged(float value)
+            {
+                this.ScaleTo(new Vector2(value), 500, Easing.Out);
+                this.ResizeTo(new Vector2(1 / value), 500, Easing.Out);
+            }
+        }
+
         [BackgroundDependencyLoader]
         private void load(OsuConfigManager config)
         {
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index bb356ce7f0..58af93a88b 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -359,7 +359,10 @@ namespace osu.Game
                 {
                     RelativeSizeAxes = Axes.Both,
                 },
-                mainContent = new DrawSizePreservingFillContainer(),
+                mainContent = new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                },
                 overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue },
                 idleTracker = new IdleTracker(6000)
             });
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 3fa4276616..b336dec848 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -63,9 +63,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y
                 },
+                new SettingsSlider<float, UIScaleSlider>
+                {
+                    LabelText = "UI Scaling",
+                    TransferValueOnCommit = true,
+                    Bindable = osuConfig.GetBindable<float>(OsuSetting.UIScale),
+                    KeyboardStep = 0.01f
+                },
                 new SettingsEnumDropdown<ScalingMode>
                 {
-                    LabelText = "Scaling",
+                    LabelText = "Screen Scaling",
                     Bindable = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
                 },
                 scalingSettings = new FillFlowContainer<SettingsSlider<float>>
@@ -141,6 +148,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
 
                 scalingSettings.ForEach(s => s.TransferValueOnCommit = mode == ScalingMode.Everything);
+
             }, true);
         }
 
@@ -202,6 +210,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
             }
         }
 
+        private class UIScaleSlider : OsuSliderBar<float>
+        {
+            public override string TooltipText => base.TooltipText + "x";
+        }
+
         private class ResolutionSettingsDropdown : SettingsDropdown<Size>
         {
             protected override OsuDropdown<Size> CreateDropdown() => new ResolutionDropdownControl { Items = Items };

From 5e4bea9d99e5bc422227f2e512a5a74203fd7a5b Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 10 Jan 2019 12:11:14 +0900
Subject: [PATCH 41/43] Fix extra newline

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

diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index b336dec848..d59e2e033e 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -148,7 +148,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
                     scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
 
                 scalingSettings.ForEach(s => s.TransferValueOnCommit = mode == ScalingMode.Everything);
-
             }, true);
         }
 

From 4578d36a67bd5817f642029c98b4e108b546332e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 10 Jan 2019 14:55:36 +0900
Subject: [PATCH 42/43] Add comment

---
 osu.Game/Graphics/Containers/LinkFlowContainer.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
index 7646ff723e..e4c18dfb3d 100644
--- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs
+++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
@@ -124,6 +124,9 @@ namespace osu.Game.Graphics.Containers
             });
         }
 
+        // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
+        // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation.
+        // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow.
         public override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Where(c => !(c is DrawableLinkCompiler));
     }
 }

From 3dc3d4cb40dcbfc253ef5b59f1ecaf24d5064f37 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 10 Jan 2019 15:25:07 +0900
Subject: [PATCH 43/43] Add test

---
 .../Visual/TestCasePlaySongSelect.cs          | 29 +++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
index 29060ceb12..369d28fc91 100644
--- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
@@ -57,11 +57,19 @@ namespace osu.Game.Tests.Visual
 
         private class TestSongSelect : PlaySongSelect
         {
+            public Action StartRequested;
+
             public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
 
             public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
             public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
             public new BeatmapCarousel Carousel => base.Carousel;
+
+            protected override bool OnStart()
+            {
+                StartRequested?.Invoke();
+                return base.OnStart();
+            }
         }
 
         private TestSongSelect songSelect;
@@ -182,6 +190,27 @@ namespace osu.Game.Tests.Visual
             void onRulesetChange(RulesetInfo ruleset) => rulesetChangeIndex = actionIndex--;
         }
 
+        [Test]
+        public void TestStartAfterUnMatchingFilterDoesNotStart()
+        {
+            addManyTestMaps();
+            AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "has selection");
+
+            bool startRequested = false;
+
+            AddStep("set filter and finalize", () =>
+            {
+                songSelect.StartRequested = () => startRequested = true;
+
+                songSelect.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" });
+                songSelect.FinaliseSelection();
+
+                songSelect.StartRequested = null;
+            });
+
+            AddAssert("start not requested", () => !startRequested);
+        }
+
         private void importForRuleset(int id) => AddStep($"import test map for ruleset {id}", () => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())));
 
         private static int importId;