diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 4d9dbbbc5f..d99325ff87 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -71,8 +71,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
 
         protected override Skill[] CreateSkills(IBeatmap beatmap)
         {
-            using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty))
-                halfCatcherWidth = catcher.CatchWidth * 0.5f;
+            halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
 
             return new Skill[]
             {
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 920d804e72..daf9456919 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -44,11 +44,6 @@ namespace osu.Game.Rulesets.Catch.UI
         /// </summary>
         private const float allowed_catch_range = 0.8f;
 
-        /// <summary>
-        /// Width of the area that can be used to attempt catches during gameplay.
-        /// </summary>
-        internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X) * allowed_catch_range;
-
         protected bool Dashing
         {
             get => dashing;
@@ -79,6 +74,11 @@ namespace osu.Game.Rulesets.Catch.UI
             }
         }
 
+        /// <summary>
+        /// Width of the area that can be used to attempt catches during gameplay.
+        /// </summary>
+        private readonly float catchWidth;
+
         private Container<DrawableHitObject> caughtFruit;
 
         private CatcherSprite catcherIdle;
@@ -106,7 +106,9 @@ namespace osu.Game.Rulesets.Catch.UI
 
             Size = new Vector2(CatcherArea.CATCHER_SIZE);
             if (difficulty != null)
-                Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+                Scale = calculateScale(difficulty);
+
+            catchWidth = CalculateCatchWidth(Scale);
         }
 
         [BackgroundDependencyLoader]
@@ -139,6 +141,26 @@ namespace osu.Game.Rulesets.Catch.UI
             updateCatcher();
         }
 
+        /// <summary>
+        /// Calculates the scale of the catcher based off the provided beatmap difficulty.
+        /// </summary>
+        private static Vector2 calculateScale(BeatmapDifficulty difficulty)
+            => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+
+        /// <summary>
+        /// Calculates the width of the area used for attempting catches in gameplay.
+        /// </summary>
+        /// <param name="scale">The scale of the catcher.</param>
+        internal static float CalculateCatchWidth(Vector2 scale)
+            => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range;
+
+        /// <summary>
+        /// Calculates the width of the area used for attempting catches in gameplay.
+        /// </summary>
+        /// <param name="difficulty">The beatmap difficulty.</param>
+        internal static float CalculateCatchWidth(BeatmapDifficulty difficulty)
+            => CalculateCatchWidth(calculateScale(difficulty));
+
         /// <summary>
         /// Add a caught fruit to the catcher's stack.
         /// </summary>
@@ -177,7 +199,7 @@ namespace osu.Game.Rulesets.Catch.UI
         /// <returns>Whether the catch is possible.</returns>
         public bool AttemptCatch(CatchHitObject fruit)
         {
-            var halfCatchWidth = CatchWidth * 0.5f;
+            var halfCatchWidth = catchWidth * 0.5f;
 
             // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
             var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs
index c68015d1a2..7b4747592a 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Osu;
 using osu.Game.Rulesets.Osu.Objects;
 using osu.Game.Screens.Edit;
-using osuTK.Input;
 
 namespace osu.Game.Tests.Visual.Editor
 {
@@ -20,6 +19,7 @@ namespace osu.Game.Tests.Visual.Editor
         }
 
         private EditorBeatmap editorBeatmap;
+        private TestEditor editor;
 
         public override void SetUpSteps()
         {
@@ -153,36 +153,15 @@ namespace osu.Game.Tests.Visual.Editor
             AddAssert("no hitobject added", () => addedObject == null);
         }
 
-        private void addUndoSteps()
+        private void addUndoSteps() => AddStep("undo", () => editor.Undo());
+
+        private void addRedoSteps() => AddStep("redo", () => editor.Redo());
+
+        private class TestEditor : Screens.Edit.Editor
         {
-            AddStep("press undo", () =>
-            {
-                InputManager.PressKey(Key.LControl);
-                InputManager.PressKey(Key.Z);
-            });
+            public new void Undo() => base.Undo();
 
-            AddStep("release keys", () =>
-            {
-                InputManager.ReleaseKey(Key.LControl);
-                InputManager.ReleaseKey(Key.Z);
-            });
-        }
-
-        private void addRedoSteps()
-        {
-            AddStep("press redo", () =>
-            {
-                InputManager.PressKey(Key.LControl);
-                InputManager.PressKey(Key.LShift);
-                InputManager.PressKey(Key.Z);
-            });
-
-            AddStep("release keys", () =>
-            {
-                InputManager.ReleaseKey(Key.LControl);
-                InputManager.ReleaseKey(Key.LShift);
-                InputManager.ReleaseKey(Key.Z);
-            });
+            public new void Redo() => base.Redo();
         }
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index e847dcec40..0047142cbd 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -4,7 +4,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Reflection;
 using JetBrains.Annotations;
 using osu.Framework.Allocation;
 using osu.Framework.Bindables;
@@ -180,11 +179,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
 
         private void apply(HitObject hitObject)
         {
-#pragma warning disable 618 // can be removed 20200417
-            if (GetType().GetMethod(nameof(AddNested), BindingFlags.NonPublic | BindingFlags.Instance)?.DeclaringType != typeof(DrawableHitObject))
-                return;
-#pragma warning restore 618
-
             if (nestedHitObjects.IsValueCreated)
             {
                 nestedHitObjects.Value.Clear();
@@ -195,7 +189,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
             {
                 var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
 
-                addNested(drawableNested);
+                drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r);
+                drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r);
+                drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
+
+                nestedHitObjects.Value.Add(drawableNested);
                 AddNestedHitObject(drawableNested);
             }
         }
@@ -208,13 +206,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
         {
         }
 
-        /// <summary>
-        /// Adds a nested <see cref="DrawableHitObject"/>. This should not be used except for legacy nested <see cref="DrawableHitObject"/> usages.
-        /// </summary>
-        /// <param name="h"></param>
-        [Obsolete("Use AddNestedHitObject() / ClearNestedHitObjects() / CreateNestedHitObject() instead.")] // can be removed 20200417
-        protected virtual void AddNested(DrawableHitObject h) => addNested(h);
-
         /// <summary>
         /// Invoked by the base <see cref="DrawableHitObject"/> to remove all previously-added nested <see cref="DrawableHitObject"/>s.
         /// </summary>
@@ -229,17 +220,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
         /// <returns>The drawable representation for <paramref name="hitObject"/>.</returns>
         protected virtual DrawableHitObject CreateNestedHitObject(HitObject hitObject) => null;
 
-        private void addNested(DrawableHitObject hitObject)
-        {
-            // Todo: Exists for legacy purposes, can be removed 20200417
-
-            hitObject.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r);
-            hitObject.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r);
-            hitObject.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
-
-            nestedHitObjects.Value.Add(hitObject);
-        }
-
         #region State / Transform Management
 
         /// <summary>
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 9a1f450dc6..54e4af94a4 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -22,6 +22,7 @@ using osu.Game.Screens.Edit.Design;
 using osuTK.Input;
 using System.Collections.Generic;
 using osu.Framework;
+using osu.Framework.Input;
 using osu.Framework.Input.Bindings;
 using osu.Framework.Logging;
 using osu.Game.Beatmaps;
@@ -37,7 +38,7 @@ using osu.Game.Users;
 namespace osu.Game.Screens.Edit
 {
     [Cached(typeof(IBeatSnapProvider))]
-    public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IBeatSnapProvider
+    public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider
     {
         public override float BackgroundParallaxAmount => 0.1f;
 
@@ -157,8 +158,8 @@ namespace osu.Game.Screens.Edit
                                 {
                                     Items = new[]
                                     {
-                                        undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, undo),
-                                        redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, redo)
+                                        undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, Undo),
+                                        redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo)
                                     }
                                 }
                             }
@@ -230,6 +231,30 @@ namespace osu.Game.Screens.Edit
             clock.ProcessFrame();
         }
 
+        public bool OnPressed(PlatformAction action)
+        {
+            switch (action.ActionType)
+            {
+                case PlatformActionType.Undo:
+                    Undo();
+                    return true;
+
+                case PlatformActionType.Redo:
+                    Redo();
+                    return true;
+
+                case PlatformActionType.Save:
+                    saveBeatmap();
+                    return true;
+            }
+
+            return false;
+        }
+
+        public void OnReleased(PlatformAction action)
+        {
+        }
+
         protected override bool OnKeyDown(KeyDownEvent e)
         {
             switch (e.Key)
@@ -241,28 +266,6 @@ namespace osu.Game.Screens.Edit
                 case Key.Right:
                     seek(e, 1);
                     return true;
-
-                case Key.S:
-                    if (e.ControlPressed)
-                    {
-                        saveBeatmap();
-                        return true;
-                    }
-
-                    break;
-
-                case Key.Z:
-                    if (e.ControlPressed)
-                    {
-                        if (e.ShiftPressed)
-                            redo();
-                        else
-                            undo();
-
-                        return true;
-                    }
-
-                    break;
             }
 
             return base.OnKeyDown(e);
@@ -326,9 +329,9 @@ namespace osu.Game.Screens.Edit
             return base.OnExiting(next);
         }
 
-        private void undo() => changeHandler.RestoreState(-1);
+        protected void Undo() => changeHandler.RestoreState(-1);
 
-        private void redo() => changeHandler.RestoreState(1);
+        protected void Redo() => changeHandler.RestoreState(1);
 
         private void resetTrack(bool seekToStart = false)
         {