diff --git a/osu-framework b/osu-framework index 67d89a3601..5c22092e59 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 67d89a36016f98c0ede576b859a2ccafe114fce8 +Subproject commit 5c22092e590d589927962b8d0173dae5f9b1405c diff --git a/osu.Desktop.Tests/Visual/TestCaseHitObjects.cs b/osu.Desktop.Tests/Visual/TestCaseHitObjects.cs index df5f9f65b8..13e05d6477 100644 --- a/osu.Desktop.Tests/Visual/TestCaseHitObjects.cs +++ b/osu.Desktop.Tests/Visual/TestCaseHitObjects.cs @@ -2,60 +2,38 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using OpenTK; -using OpenTK.Graphics; +using osu.Game.Rulesets.Osu; +using osu.Framework.Allocation; +using osu.Game.Rulesets; namespace osu.Desktop.Tests.Visual { internal class TestCaseHitObjects : OsuTestCase { - private readonly FramedClock framedClock; + private FramedClock framedClock; private bool auto; - public TestCaseHitObjects() + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) { var rateAdjustClock = new StopwatchClock(true); framedClock = new FramedClock(rateAdjustClock); - playbackSpeed.ValueChanged += delegate { rateAdjustClock.Rate = playbackSpeed.Value; }; - - playbackSpeed.TriggerChange(); AddStep(@"circles", () => loadHitobjects(HitObjectType.Circle)); AddStep(@"slider", () => loadHitobjects(HitObjectType.Slider)); AddStep(@"spinner", () => loadHitobjects(HitObjectType.Spinner)); - AddToggleStep(@"auto", state => { auto = state; loadHitobjects(mode); }); - - BasicSliderBar sliderBar; - Add(new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new SpriteText { Text = "Playback Speed" }, - sliderBar = new BasicSliderBar - { - Width = 150, - Height = 10, - SelectionColor = Color4.Orange, - } - } - }); - - sliderBar.Current.BindTo(playbackSpeed); + AddToggleStep("Auto", state => { auto = state; loadHitobjects(mode); }); + AddSliderStep("Playback speed", 0.0, 2.0, 0.5, v => rateAdjustClock.Rate = v); framedClock.ProcessFrame(); @@ -65,7 +43,7 @@ namespace osu.Desktop.Tests.Visual Clock = framedClock, Children = new[] { - playfieldContainer = new Container { RelativeSizeAxes = Axes.Both }, + playfieldContainer = new OsuInputManager(rulesets.GetRuleset(0)) { RelativeSizeAxes = Axes.Both }, approachContainer = new Container { RelativeSizeAxes = Axes.Both } } }; @@ -75,9 +53,8 @@ namespace osu.Desktop.Tests.Visual private HitObjectType mode = HitObjectType.Slider; - private readonly BindableNumber playbackSpeed = new BindableDouble(0.5) { MinValue = 0, MaxValue = 1 }; - private readonly Container playfieldContainer; - private readonly Container approachContainer; + private Container playfieldContainer; + private Container approachContainer; private void loadHitobjects(HitObjectType mode) { diff --git a/osu.Desktop.Tests/Visual/TestCaseKeyConfiguration.cs b/osu.Desktop.Tests/Visual/TestCaseKeyConfiguration.cs new file mode 100644 index 0000000000..cab285c72e --- /dev/null +++ b/osu.Desktop.Tests/Visual/TestCaseKeyConfiguration.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays; + +namespace osu.Desktop.Tests.Visual +{ + public class TestCaseKeyConfiguration : OsuTestCase + { + private readonly KeyBindingOverlay overlay; + + public override string Description => @"Key configuration"; + + public TestCaseKeyConfiguration() + { + Child = overlay = new KeyBindingOverlay(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + overlay.Show(); + } + } +} diff --git a/osu.Desktop.Tests/Visual/TestCaseKeyCounter.cs b/osu.Desktop.Tests/Visual/TestCaseKeyCounter.cs index efb662d3b9..1861a77a3a 100644 --- a/osu.Desktop.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Desktop.Tests/Visual/TestCaseKeyCounter.cs @@ -1,11 +1,8 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.MathUtils; using osu.Game.Screens.Play; @@ -34,34 +31,13 @@ namespace osu.Desktop.Tests.Visual new KeyCounterMouse(MouseButton.Right), }, }; - BindableInt bindable = new BindableInt { MinValue = 0, MaxValue = 200, Default = 50 }; - bindable.ValueChanged += delegate { kc.FadeTime = bindable.Value; }; - AddStep("Add Random", () => + + AddStep("Add random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); kc.Add(new KeyCounterKeyboard(key)); }); - - TestSliderBar sliderBar; - - Add(new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new SpriteText { Text = "FadeTime" }, - sliderBar = new TestSliderBar - { - Width = 150, - Height = 10, - SelectionColor = Color4.Orange, - } - } - }); - - sliderBar.Current.BindTo(bindable); + AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v); Add(kc); } diff --git a/osu.Desktop.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Desktop.Tests/Visual/TestCaseNotificationOverlay.cs index b9e492593f..1e46fbda35 100644 --- a/osu.Desktop.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Desktop.Tests/Visual/TestCaseNotificationOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Desktop.Tests.Visual while (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) { - var p = progressingNotifications.FirstOrDefault(n => n.IsLoaded && n.State == ProgressNotificationState.Queued); + var p = progressingNotifications.FirstOrDefault(n => n.IsAlive && n.State == ProgressNotificationState.Queued); if (p == null) break; diff --git a/osu.Desktop.Tests/Visual/TestCaseSettings.cs b/osu.Desktop.Tests/Visual/TestCaseSettings.cs index 7b35009aef..1f4b88e9e3 100644 --- a/osu.Desktop.Tests/Visual/TestCaseSettings.cs +++ b/osu.Desktop.Tests/Visual/TestCaseSettings.cs @@ -13,7 +13,7 @@ namespace osu.Desktop.Tests.Visual public TestCaseSettings() { - Children = new[] { settings = new SettingsOverlay() }; + Children = new[] { settings = new MainSettings() }; } protected override void LoadComplete() diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj index 3111088ff6..24d112a45c 100644 --- a/osu.Desktop.Tests/osu.Desktop.Tests.csproj +++ b/osu.Desktop.Tests/osu.Desktop.Tests.csproj @@ -81,6 +81,7 @@ + diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 18fa43ab5c..b445340f50 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -27,8 +27,6 @@ namespace osu.Desktop.Overlays private UpdateManager updateManager; private NotificationOverlay notificationOverlay; - protected override bool HideOnEscape => false; - public override bool HandleInput => false; [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index eada5cf532..2a913a77d3 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -1,32 +1,27 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; -using osu.Game.Input; -using OpenTK.Input; +using System.ComponentModel; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; namespace osu.Game.Rulesets.Catch { - public class CatchInputManager : ActionMappingInputManager + public class CatchInputManager : DatabasedKeyBindingInputManager { - public CatchInputManager(RulesetInfo ruleset) : base(ruleset) + public CatchInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) { - Mappings = new Dictionary - { - { Key.Z, CatchAction.MoveLeft }, - { Key.Left, CatchAction.MoveLeft }, - { Key.X, CatchAction.MoveRight }, - { Key.Right, CatchAction.MoveRight }, - { Key.LShift, CatchAction.Dash }, - { Key.RShift, CatchAction.Dash }, - }; } } public enum CatchAction { + [Description("Move left")] MoveLeft, + [Description("Move right")] MoveRight, + [Description("Engage dash")] Dash } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 8d45ea8fa2..b486566b7d 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Scoring; +using osu.Framework.Input.Bindings; namespace osu.Game.Rulesets.Catch { @@ -20,6 +21,16 @@ namespace osu.Game.Rulesets.Catch { public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchRulesetContainer(this, beatmap, isForCurrentRuleset); + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(Key.Z, CatchAction.MoveLeft), + new KeyBinding(Key.Left, CatchAction.MoveLeft), + new KeyBinding(Key.X, CatchAction.MoveRight), + new KeyBinding(Key.Right, CatchAction.MoveRight), + new KeyBinding(Key.LShift, CatchAction.Dash), + new KeyBinding(Key.RShift, CatchAction.Dash), + }; + public override IEnumerable GetModsFor(ModType type) { switch (type) diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 27cc05c47a..1d037deaef 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override Playfield CreatePlayfield() => new CatchPlayfield(); - protected override PassThroughInputManager CreateActionMappingInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + public override PassThroughInputManager CreateKeyBindingInputManager() => new CatchInputManager(Ruleset?.RulesetInfo); protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h) { diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 776f2119ab..f416f6acfb 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.MathUtils; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch.UI catcher.Size = new Vector2(DrawSize.Y); } - private class Catcher : Container + private class Catcher : Container, IKeyBindingHandler { private Texture texture; @@ -104,48 +104,40 @@ namespace osu.Game.Rulesets.Catch.UI OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly. }; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + public bool OnPressed(CatchAction action) { - if (args.Repeat) return true; - - if (state.Data is CatchAction) + switch (action) { - switch ((CatchAction)state.Data) - { - case CatchAction.MoveLeft: - currentDirection--; - return true; - case CatchAction.MoveRight: - currentDirection++; - return true; - case CatchAction.Dash: - Dashing = true; - return true; - } + case CatchAction.MoveLeft: + currentDirection--; + return true; + case CatchAction.MoveRight: + currentDirection++; + return true; + case CatchAction.Dash: + Dashing = true; + return true; } - return base.OnKeyDown(state, args); + return false; } - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + public bool OnReleased(CatchAction action) { - if (state.Data is CatchAction) + switch (action) { - switch ((CatchAction)state.Data) - { - case CatchAction.MoveLeft: - currentDirection++; - return true; - case CatchAction.MoveRight: - currentDirection--; - return true; - case CatchAction.Dash: - Dashing = false; - return true; - } + case CatchAction.MoveLeft: + currentDirection++; + return true; + case CatchAction.MoveRight: + currentDirection--; + return true; + case CatchAction.Dash: + Dashing = false; + return true; } - return base.OnKeyUp(state, args); + return false; } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 3004dafda7..0c3c4f0a6d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -7,12 +7,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input; +using osu.Framework.Input.Bindings; using OpenTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class CirclePiece : Container + public class CirclePiece : Container, IKeyBindingHandler { private readonly Sprite disc; @@ -49,9 +49,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces disc.Texture = textures.Get(@"Play/osu/disc"); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + public bool OnPressed(OsuAction action) { - return Hit?.Invoke() ?? false; + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + return IsHovered && (Hit?.Invoke() ?? false); + } + + return false; } + + public bool OnReleased(OsuAction action) => false; } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 8b9441ea65..680f987274 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; accentColour = value; - if (LoadState == LoadState.Loaded) + if (LoadState == LoadState.Ready) Schedule(reloadTexture); } } diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs new file mode 100644 index 0000000000..a29b128e07 --- /dev/null +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using System.Linq; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; +using OpenTK.Input; +using KeyboardState = osu.Framework.Input.KeyboardState; +using MouseState = osu.Framework.Input.MouseState; + +namespace osu.Game.Rulesets.Osu +{ + public class OsuInputManager : DatabasedKeyBindingInputManager + { + public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + + } + protected override void TransformState(InputState state) + { + base.TransformState(state); + + var mouse = state.Mouse as MouseState; + var keyboard = state.Keyboard as KeyboardState; + + if (mouse != null && keyboard != null) + { + if (mouse.IsPressed(MouseButton.Left)) + keyboard.Keys = keyboard.Keys.Concat(new[] { Key.LastKey + 1 }); + if (mouse.IsPressed(MouseButton.Right)) + keyboard.Keys = keyboard.Keys.Concat(new[] { Key.LastKey + 2 }); + } + } + } + + public enum OsuAction + { + [Description("Left Button")] + LeftButton, + [Description("Right Button")] + RightButton + } +} diff --git a/osu.Game.Rulesets.Osu/OsuKeyConversionInputManager.cs b/osu.Game.Rulesets.Osu/OsuKeyConversionInputManager.cs deleted file mode 100644 index 10adca4e43..0000000000 --- a/osu.Game.Rulesets.Osu/OsuKeyConversionInputManager.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Input; -using OpenTK.Input; -using KeyboardState = osu.Framework.Input.KeyboardState; -using MouseState = osu.Framework.Input.MouseState; - -namespace osu.Game.Rulesets.Osu -{ - public class OsuKeyConversionInputManager : PassThroughInputManager - { - private bool leftViaKeyboard; - private bool rightViaKeyboard; - - protected override void TransformState(InputState state) - { - base.TransformState(state); - - var mouse = state.Mouse as MouseState; - var keyboard = state.Keyboard as KeyboardState; - - if (keyboard != null) - { - leftViaKeyboard = keyboard.Keys.Contains(Key.Z); - rightViaKeyboard = keyboard.Keys.Contains(Key.X); - } - - if (mouse != null) - { - if (leftViaKeyboard) - mouse.SetPressed(MouseButton.Left, true); - if (rightViaKeyboard) - mouse.SetPressed(MouseButton.Right, true); - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 212c634aaf..75b7be01a4 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Overlays.Settings; +using osu.Framework.Input.Bindings; namespace osu.Game.Rulesets.Osu { @@ -24,8 +25,16 @@ namespace osu.Game.Rulesets.Osu { public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new OsuRulesetContainer(this, beatmap, isForCurrentRuleset); - public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new[] + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { + new KeyBinding(Key.Z, OsuAction.LeftButton), + new KeyBinding(Key.X, OsuAction.RightButton), + new KeyBinding(Key.LastKey + 1, OsuAction.LeftButton), + new KeyBinding(Key.LastKey + 2, OsuAction.RightButton), + }; + + public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new[] + { new BeatmapStatistic { Name = @"Circle count", diff --git a/osu.Game/Graphics/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs similarity index 96% rename from osu.Game/Graphics/Cursor/CursorTrail.cs rename to osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 035f6f98b0..a5be6a7952 100644 --- a/osu.Game/Graphics/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -1,22 +1,22 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.OpenGL.Buffers; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.Input; -using OpenTK; -using System; -using osu.Framework.Graphics.OpenGL.Buffers; -using OpenTK.Graphics.ES30; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Colour; using osu.Framework.Timing; -using System.Diagnostics; -using osu.Framework.Graphics.OpenGL.Vertices; +using OpenTK; +using OpenTK.Graphics.ES30; -namespace osu.Game.Graphics.Cursor +namespace osu.Game.Rulesets.Osu.UI.Cursor { internal class CursorTrail : Drawable { diff --git a/osu.Game/Graphics/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs similarity index 82% rename from osu.Game/Graphics/Cursor/GameplayCursor.cs rename to osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 9f771ae56d..adfc946f86 100644 --- a/osu.Game/Graphics/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -1,8 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; @@ -10,13 +8,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Configuration; +using OpenTK; +using OpenTK.Graphics; -namespace osu.Game.Graphics.Cursor +namespace osu.Game.Rulesets.Osu.UI.Cursor { - public class GameplayCursor : CursorContainer + public class GameplayCursor : CursorContainer, IKeyBindingHandler { protected override Drawable CreateCursor() => new OsuCursor(); @@ -25,19 +25,7 @@ namespace osu.Game.Graphics.Cursor Add(new CursorTrail { Depth = 1 }); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - ActiveCursor.Scale = new Vector2(1); - ActiveCursor.ScaleTo(1.2f, 100, Easing.OutQuad); - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - if (!state.Mouse.HasMainButtonPressed) - ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); - return base.OnMouseUp(state, args); - } + private int downCount; public class OsuCursor : Container { @@ -143,5 +131,33 @@ namespace osu.Game.Graphics.Cursor cursorContainer.Scale = new Vector2(scale); } } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + downCount++; + ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); + break; + } + + return false; + } + + public bool OnReleased(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (--downCount == 0) + ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); + break; + } + + return false; + } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2604b1ee8a..9b88c9d1b3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -10,8 +10,8 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using System.Linq; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.UI.Cursor; namespace osu.Game.Rulesets.Osu.UI { diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index d029524a32..917322de44 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override Playfield CreatePlayfield() => new OsuPlayfield(); - protected override PassThroughInputManager CreateActionMappingInputManager() => new OsuKeyConversionInputManager(); + public override PassThroughInputManager CreateKeyBindingInputManager() => new OsuInputManager(Ruleset?.RulesetInfo); protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index b24d5b3a4a..1422ded407 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -77,7 +77,9 @@ - + + + diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index bbb6c975d0..e388f5b6d1 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -307,6 +307,11 @@ namespace osu.Game.Beatmaps /// The imported beatmap, or an existing instance if it is already present. private BeatmapSetInfo importToStorage(ArchiveReader reader) { + // let's make sure there are actually .osu files to import. + string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); + if (string.IsNullOrEmpty(mapName)) + throw new InvalidOperationException("No beatmap files found in the map folder."); + // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) @@ -339,7 +344,7 @@ namespace osu.Game.Beatmaps BeatmapMetadata metadata; - using (var stream = new StreamReader(reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu"))))) + using (var stream = new StreamReader(reader.GetStream(mapName))) metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; beatmapSet = new BeatmapSetInfo diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 1c3eadc91e..234d65eee4 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -19,7 +19,9 @@ namespace osu.Game.Beatmaps.Formats public static BeatmapDecoder GetDecoder(StreamReader stream) { - string line = stream.ReadLine()?.Trim(); + string line; + do { line = stream.ReadLine()?.Trim(); } + while (line != null && line.Length == 0); if (line == null || !decoders.ContainsKey(line)) throw new IOException(@"Unknown file format"); diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 433c23284f..093f607e0d 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -27,7 +27,9 @@ namespace osu.Game.Beatmaps.Formats AddDecoder(@"osu file format v7"); AddDecoder(@"osu file format v6"); AddDecoder(@"osu file format v5"); - // TODO: Not sure how far back to go, or differences between versions + AddDecoder(@"osu file format v4"); + AddDecoder(@"osu file format v3"); + // TODO: differences between versions } private ConvertHitObjectParser parser; @@ -222,6 +224,7 @@ namespace osu.Game.Beatmaps.Formats { while (line.IndexOf('$') >= 0) { + string origLine = line; string[] split = line.Split(','); for (int i = 0; i < split.Length; i++) { @@ -231,6 +234,7 @@ namespace osu.Game.Beatmaps.Formats } line = string.Join(",", split); + if (line == origLine) break; } } @@ -327,6 +331,7 @@ namespace osu.Game.Beatmaps.Formats if (speedMultiplier != difficultyPoint.SpeedMultiplier) { + beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { Time = time, diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index ec9d30f244..3e0ed4b059 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -34,9 +34,8 @@ namespace osu.Game.Graphics.Containers protected override Container Content => content; [BackgroundDependencyLoader] - private void load(UserInputManager input, OsuConfigManager config) + private void load(OsuConfigManager config) { - this.input = input; parallaxEnabled = config.GetBindable(OsuSetting.MenuParallax); parallaxEnabled.ValueChanged += delegate { @@ -48,6 +47,12 @@ namespace osu.Game.Graphics.Containers }; } + protected override void LoadComplete() + { + base.LoadComplete(); + input = GetContainingInputManager(); + } + private bool firstUpdate = true; protected override void Update() diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 447daca75e..fe060f70f0 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -3,10 +3,8 @@ using OpenTK.Graphics; using OpenTK.Input; -using osu.Framework.Allocation; using osu.Framework.Input; using System; -using System.Linq; namespace osu.Game.Graphics.UserInterface { @@ -28,34 +26,28 @@ namespace osu.Game.Graphics.UserInterface { focus = value; if (!focus && HasFocus) - inputManager.ChangeFocus(null); + GetContainingInputManager().ChangeFocus(null); } } - private InputManager inputManager; - - [BackgroundDependencyLoader] - private void load(UserInputManager inputManager) - { - this.inputManager = inputManager; - } - protected override void OnFocus(InputState state) { base.OnFocus(state); BorderThickness = 0; } - protected override void OnFocusLost(InputState state) + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (state.Keyboard.Keys.Any(key => key == Key.Escape)) + if (args.Key == Key.Escape) { if (Text.Length > 0) Text = string.Empty; else Exit?.Invoke(); + return true; } - base.OnFocusLost(state); + + return base.OnKeyDown(state, args); } public override bool RequestsFocus => HoldFocus; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 3c454f2af2..5650fc6f48 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -16,7 +17,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { - public class OsuButton : Button + public class OsuButton : Button, IFilterable { private Box hover; @@ -108,5 +109,15 @@ namespace osu.Game.Graphics.UserInterface Content.ScaleTo(1, 1000, Easing.OutElastic); return base.OnMouseUp(state, args); } + + public string[] FilterTerms => new[] { Text }; + + public bool MatchingFilter + { + set + { + this.FadeTo(value ? 1 : 0); + } + } } } \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index fa192b0825..cd77fb9f5b 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -15,8 +15,6 @@ namespace osu.Game.Graphics.UserInterface.Volume { private readonly VolumeMeter volumeMeterMaster; - protected override bool HideOnEscape => false; - private void volumeChanged(double newVolume) { Show(); diff --git a/osu.Game/Input/ActionMappingInputManager.cs b/osu.Game/Input/ActionMappingInputManager.cs deleted file mode 100644 index c918982fab..0000000000 --- a/osu.Game/Input/ActionMappingInputManager.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Input; -using osu.Game.Rulesets; -using OpenTK.Input; - -namespace osu.Game.Input -{ - /// - /// Maps custom action data of type and stores to . - /// - /// The type of the custom action. - public class ActionMappingInputManager : PassThroughInputManager - where T : struct - { - private readonly RulesetInfo ruleset; - - private readonly int? variant; - - /// - /// Create a new instance. - /// - /// A reference to identify the current . Used to lookup mappings. Null for global mappings. - /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. - protected ActionMappingInputManager(RulesetInfo ruleset = null, int? variant = null) - { - this.ruleset = ruleset; - this.variant = variant; - } - - protected IDictionary Mappings { get; set; } - - [BackgroundDependencyLoader] - private void load(BindingStore bindings) - { - var rulesetId = ruleset?.ID; - foreach (var b in bindings.Query(b => b.RulesetID == rulesetId && b.Variant == variant)) - Mappings[b.Key] = (T)(object)b.Action; - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - mapKey(state, args.Key); - return base.OnKeyDown(state, args); - } - - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) - { - mapKey(state, args.Key); - return base.OnKeyUp(state, args); - } - - private void mapKey(InputState state, Key key) - { - T mappedData; - if (Mappings.TryGetValue(key, out mappedData)) - state.Data = mappedData; - } - - private T parseStringRepresentation(string str) - { - T res; - - if (Enum.TryParse(str, out res)) - return res; - - return default(T); - } - } -} diff --git a/osu.Game/Input/Binding.cs b/osu.Game/Input/Binding.cs deleted file mode 100644 index e887d15a65..0000000000 --- a/osu.Game/Input/Binding.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets; -using OpenTK.Input; -using SQLite.Net.Attributes; -using SQLiteNetExtensions.Attributes; - -namespace osu.Game.Input -{ - public class Binding - { - [ForeignKey(typeof(RulesetInfo))] - public int? RulesetID { get; set; } - - [Indexed] - public int? Variant { get; set; } - - public Key Key { get; set; } - - public int Action { get; set; } - } -} \ No newline at end of file diff --git a/osu.Game/Input/BindingStore.cs b/osu.Game/Input/BindingStore.cs deleted file mode 100644 index aa47bee068..0000000000 --- a/osu.Game/Input/BindingStore.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Platform; -using osu.Game.Database; -using SQLite.Net; - -namespace osu.Game.Input -{ - public class BindingStore : DatabaseBackedStore - { - public BindingStore(SQLiteConnection connection, Storage storage = null) - : base(connection, storage) - { - } - - protected override void Prepare(bool reset = false) - { - Connection.CreateTable(); - } - - protected override Type[] ValidTypes => new[] - { - typeof(Binding) - }; - - } -} \ No newline at end of file diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs new file mode 100644 index 0000000000..cbf74d6984 --- /dev/null +++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets; +using SQLite.Net.Attributes; +using SQLiteNetExtensions.Attributes; + +namespace osu.Game.Input.Bindings +{ + [Table("KeyBinding")] + public class DatabasedKeyBinding : KeyBinding + { + [PrimaryKey, AutoIncrement] + public int ID { get; set; } + + [ForeignKey(typeof(RulesetInfo))] + public int? RulesetID { get; set; } + + [Indexed] + public int? Variant { get; set; } + + [Column("Keys")] + public string KeysString + { + get { return KeyCombination.ToString(); } + private set { KeyCombination = value; } + } + + [Indexed] + [Column("Action")] + public int IntAction + { + get { return (int)Action; } + set { Action = value; } + } + } +} \ No newline at end of file diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs new file mode 100644 index 0000000000..0a4fcf4389 --- /dev/null +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets; + +namespace osu.Game.Input.Bindings +{ + /// + /// A KeyBindingInputManager with a database backing for custom overrides. + /// + /// The type of the custom action. + public abstract class DatabasedKeyBindingInputManager : KeyBindingInputManager + where T : struct + { + private readonly RulesetInfo ruleset; + + private readonly int? variant; + + private KeyBindingStore store; + + public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(); + + /// + /// Create a new instance. + /// + /// A reference to identify the current . Used to lookup mappings. Null for global mappings. + /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. + /// Specify how to deal with multiple matches of s and s. + protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) + : base(simultaneousMode) + { + this.ruleset = ruleset; + this.variant = variant; + + if (ruleset != null && variant == null) + throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); + } + + [BackgroundDependencyLoader] + private void load(KeyBindingStore keyBindings) + { + store = keyBindings; + } + + protected override void ReloadMappings() + { + KeyBindings = store.Query(ruleset?.ID, variant); + } + } +} \ No newline at end of file diff --git a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs new file mode 100644 index 0000000000..31be2e6adc --- /dev/null +++ b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Input; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; + +namespace osu.Game.Input.Bindings +{ + public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager + { + private readonly Drawable handler; + + public GlobalKeyBindingInputManager(OsuGameBase game) + { + if (game is IKeyBindingHandler) + handler = game; + } + + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(Key.F8, GlobalAction.ToggleChat), + new KeyBinding(Key.F9, GlobalAction.ToggleSocial), + new KeyBinding(new[] { Key.LControl, Key.LAlt, Key.R }, GlobalAction.ResetInputSettings), + new KeyBinding(new[] { Key.LControl, Key.T }, GlobalAction.ToggleToolbar), + new KeyBinding(new[] { Key.LControl, Key.O }, GlobalAction.ToggleSettings), + new KeyBinding(new[] { Key.LControl, Key.D }, GlobalAction.ToggleDirect), + }; + + protected override IEnumerable GetKeyboardInputQueue() => + handler == null ? base.GetKeyboardInputQueue() : new[] { handler }.Concat(base.GetKeyboardInputQueue()); + } + + public enum GlobalAction + { + [Description("Toggle chat overlay")] + ToggleChat, + [Description("Toggle social overlay")] + ToggleSocial, + [Description("Reset input settings")] + ResetInputSettings, + [Description("Toggle toolbar")] + ToggleToolbar, + [Description("Toggle settings")] + ToggleSettings, + [Description("Toggle osu!direct")] + ToggleDirect, + } +} diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs new file mode 100644 index 0000000000..f50acb4802 --- /dev/null +++ b/osu.Game/Input/KeyBindingStore.cs @@ -0,0 +1,93 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input.Bindings; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets; +using SQLite.Net; + +namespace osu.Game.Input +{ + public class KeyBindingStore : DatabaseBackedStore + { + public KeyBindingStore(SQLiteConnection connection, RulesetStore rulesets, Storage storage = null) + : base(connection, storage) + { + foreach (var info in rulesets.Query()) + { + var ruleset = info.CreateInstance(); + foreach (var variant in ruleset.AvailableVariants) + insertDefaults(ruleset.GetDefaultKeyBindings(), info.ID, variant); + } + } + + public void Register(KeyBindingInputManager manager) => insertDefaults(manager.DefaultKeyBindings); + + protected override int StoreVersion => 3; + + protected override void PerformMigration(int currentVersion, int targetVersion) + { + base.PerformMigration(currentVersion, targetVersion); + + while (currentVersion++ < targetVersion) + { + switch (currentVersion) + { + case 1: + case 2: + case 3: + // cannot migrate; breaking underlying changes. + Reset(); + break; + } + } + } + + protected override void Prepare(bool reset = false) + { + if (reset) + Connection.DropTable(); + + Connection.CreateTable(); + } + + private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) + { + var query = Query(rulesetId, variant); + + // compare counts in database vs defaults + foreach (var group in defaults.GroupBy(k => k.Action)) + { + int count; + while (group.Count() > (count = query.Count(k => (int)k.Action == (int)group.Key))) + { + var insertable = group.Skip(count).First(); + + // insert any defaults which are missing. + Connection.Insert(new DatabasedKeyBinding + { + KeyCombination = insertable.KeyCombination, + Action = insertable.Action, + RulesetID = rulesetId, + Variant = variant + }); + } + } + } + + protected override Type[] ValidTypes => new[] + { + typeof(DatabasedKeyBinding) + }; + + public IEnumerable Query(int? rulesetId = null, int? variant = null) => + Query(b => b.RulesetID == rulesetId && b.Variant == variant); + + public void Update(KeyBinding keyBinding) => Connection.Update(keyBinding); + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8d8c5cf26e..e3525e423c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -9,17 +9,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; using osu.Framework.Input; -using OpenTK.Input; using osu.Framework.Logging; using osu.Game.Graphics.UserInterface.Volume; using osu.Framework.Allocation; -using osu.Framework.Timing; using osu.Game.Overlays.Toolbar; using osu.Game.Screens; using osu.Game.Screens.Menu; using OpenTK; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics; @@ -27,10 +26,11 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Screens.Play; +using osu.Game.Input.Bindings; namespace osu.Game { - public class OsuGame : OsuGameBase + public class OsuGame : OsuGameBase, IKeyBindingHandler { public Toolbar Toolbar; @@ -169,10 +169,6 @@ namespace osu.Game volume = new VolumeControl(), overlayContent = new Container { RelativeSizeAxes = Axes.Both }, new OnScreenDisplay(), - new GlobalHotkeys //exists because UserInputManager is at a level below us. - { - Handler = globalHotkeyPressed - } }); LoadComponentAsync(screenStack = new Loader(), d => @@ -186,7 +182,11 @@ namespace osu.Game LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); LoadComponentAsync(social = new SocialOverlay { Depth = -1 }, mainContent.Add); LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); - LoadComponentAsync(settings = new SettingsOverlay { Depth = -1 }, overlayContent.Add); + LoadComponentAsync(settings = new MainSettings + { + GetToolbarHeight = () => ToolbarOffset, + Depth = -1 + }, overlayContent.Add); LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); LoadComponentAsync(musicController = new MusicController { @@ -252,63 +252,43 @@ namespace osu.Game Cursor.State = Visibility.Hidden; } - private bool globalHotkeyPressed(InputState state, KeyDownEventArgs args) + public bool OnPressed(GlobalAction action) { - if (args.Repeat || intro == null) return false; + if (intro == null) return false; - switch (args.Key) + switch (action) { - case Key.F8: + case GlobalAction.ToggleChat: chat.ToggleVisibility(); return true; - case Key.F9: + case GlobalAction.ToggleSocial: social.ToggleVisibility(); return true; - case Key.PageUp: - case Key.PageDown: - var swClock = (Clock as ThrottledFrameClock)?.Source as StopwatchClock; - if (swClock == null) return false; + case GlobalAction.ResetInputSettings: + var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); - swClock.Rate *= args.Key == Key.PageUp ? 1.1f : 0.9f; - Logger.Log($@"Adjusting game clock to {swClock.Rate}", LoggingTarget.Debug); + sensitivity.Disabled = false; + sensitivity.Value = 1; + sensitivity.Disabled = true; + + frameworkConfig.Set(FrameworkSetting.ActiveInputHandlers, string.Empty); + return true; + case GlobalAction.ToggleToolbar: + Toolbar.ToggleVisibility(); + return true; + case GlobalAction.ToggleSettings: + settings.ToggleVisibility(); + return true; + case GlobalAction.ToggleDirect: + direct.ToggleVisibility(); return true; - } - - if (state.Keyboard.ControlPressed) - { - switch (args.Key) - { - case Key.R: - if (state.Keyboard.AltPressed) - { - var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); - - sensitivity.Disabled = false; - sensitivity.Value = 1; - sensitivity.Disabled = true; - - frameworkConfig.Set(FrameworkSetting.ActiveInputHandlers, string.Empty); - return true; - } - break; - case Key.T: - Toolbar.ToggleVisibility(); - return true; - case Key.O: - settings.ToggleVisibility(); - return true; - case Key.D: - if (state.Keyboard.ShiftPressed || state.Keyboard.AltPressed) - return false; - - direct.ToggleVisibility(); - return true; - } } return false; } + public bool OnReleased(GlobalAction action) => false; + public event Action ScreenChanged; private Container mainContent; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b76235f3f4..a5600d1ef7 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -20,6 +20,7 @@ using SQLite.Net; using osu.Framework.Graphics.Performance; using osu.Game.Database; using osu.Game.Input; +using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; @@ -38,7 +39,7 @@ namespace osu.Game protected ScoreStore ScoreStore; - protected BindingStore BindingStore; + protected KeyBindingStore KeyBindingStore; protected override string MainResourceFile => @"osu.Game.Resources.dll"; @@ -107,7 +108,7 @@ namespace osu.Game dependencies.Cache(FileStore = new FileStore(connection, Host.Storage)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, connection, RulesetStore, Host)); dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, connection, Host, BeatmapManager)); - dependencies.Cache(BindingStore = new BindingStore(connection)); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(connection, RulesetStore)); dependencies.Cache(new OsuColour()); //this completely overrides the framework default. will need to change once we make a proper FontStore. @@ -182,22 +183,28 @@ namespace osu.Game { base.LoadComplete(); + GlobalKeyBindingInputManager globalBinding; + base.Content.Add(new RatioAdjust { Children = new Drawable[] { Cursor = new MenuCursor(), - new OsuTooltipContainer(Cursor) + globalBinding = new GlobalKeyBindingInputManager(this) { RelativeSizeAxes = Axes.Both, - Child = content = new OsuContextMenuContainer + Child = new OsuTooltipContainer(Cursor) { RelativeSizeAxes = Axes.Both, - }, + Child = content = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both }, + } } } }); + KeyBindingStore.Register(globalBinding); + dependencies.Cache(globalBinding); + // TODO: This is temporary until we reimplement the local FPS display. // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); diff --git a/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs index 368d3cc5ef..4362b3f787 100644 --- a/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Overlays.Chat protected override void OnFocus(InputState state) { - InputManager.ChangeFocus(search); + GetContainingInputManager().ChangeFocus(search); base.OnFocus(state); } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 29b7548ada..e7a4c1c0ab 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -243,7 +243,7 @@ namespace osu.Game.Overlays protected override void OnFocus(InputState state) { //this is necessary as inputTextBox is masked away and therefore can't get focus :( - InputManager.ChangeFocus(inputTextBox); + GetContainingInputManager().ChangeFocus(inputTextBox); base.OnFocus(state); } diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs new file mode 100644 index 0000000000..7dd9919e5d --- /dev/null +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input.Bindings; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.KeyBinding +{ + public class GlobalKeyBindingsSection : KeyBindingsSection + { + private readonly string name; + + public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override string Header => name; + + public GlobalKeyBindingsSection(KeyBindingInputManager manager, string name) + { + this.name = name; + + Defaults = manager.DefaultKeyBindings; + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs new file mode 100644 index 0000000000..44aee0e666 --- /dev/null +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -0,0 +1,306 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Input; +using OpenTK.Graphics; +using OpenTK.Input; + +namespace osu.Game.Overlays.KeyBinding +{ + internal class KeyBindingRow : Container, IFilterable + { + private readonly Enum action; + private readonly IEnumerable bindings; + + private const float transition_time = 150; + + private const float height = 20; + + private const float padding = 5; + + private bool matchingFilter; + + public bool MatchingFilter + { + get { return matchingFilter; } + set + { + matchingFilter = value; + this.FadeTo(!matchingFilter ? 0 : 1); + } + } + + private OsuSpriteText text; + private OsuSpriteText pressAKey; + + private FillFlowContainer buttons; + + public string[] FilterTerms => new[] { text.Text }.Concat(bindings.Select(b => b.KeyCombination.ReadableString())).ToArray(); + + public KeyBindingRow(Enum action, IEnumerable bindings) + { + this.action = action; + this.bindings = bindings; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = padding; + } + + private KeyBindingStore store; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, KeyBindingStore store) + { + this.store = store; + + EdgeEffect = new EdgeEffectParameters + { + Radius = 2, + Colour = colours.YellowDark.Opacity(0), + Type = EdgeEffectType.Shadow, + Hollow = true, + }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + text = new OsuSpriteText + { + Text = action.GetDescription(), + Margin = new MarginPadding(padding), + }, + buttons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + }, + pressAKey = new OsuSpriteText + { + Text = "Press a key to change binding, DEL to delete, ESC to cancel.", + Y = height, + Margin = new MarginPadding(padding), + Alpha = 0, + Colour = colours.YellowDark + } + }; + + foreach (var b in bindings) + buttons.Add(new KeyButton(b)); + } + + protected override bool OnHover(InputState state) + { + this.FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + this.FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); + + base.OnHoverLost(state); + } + + public override bool AcceptsFocus => true; + + private KeyButton bindTarget; + + protected override void OnFocus(InputState state) + { + AutoSizeDuration = 500; + AutoSizeEasing = Easing.OutQuint; + + pressAKey.FadeIn(300, Easing.OutQuint); + pressAKey.Padding = new MarginPadding(); + + base.OnFocus(state); + } + + private bool isModifier(Key k) => k < Key.F1; + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + switch (args.Key) + { + case Key.Escape: + GetContainingInputManager().ChangeFocus(null); + return true; + case Key.Delete: + bindTarget.UpdateKeyCombination(Key.Unknown); + store.Update(bindTarget.KeyBinding); + GetContainingInputManager().ChangeFocus(null); + return true; + } + + if (HasFocus) + { + bindTarget.UpdateKeyCombination(state.Keyboard.Keys.ToArray()); + if (!isModifier(args.Key)) + finalise(); + return true; + } + + return base.OnKeyDown(state, args); + } + + protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + { + if (HasFocus) + { + finalise(); + return true; + } + + return base.OnKeyUp(state, args); + } + + private void finalise() + { + store.Update(bindTarget.KeyBinding); + GetContainingInputManager().ChangeFocus(null); + } + + protected override void OnFocusLost(InputState state) + { + bindTarget.IsBinding = false; + bindTarget = null; + + pressAKey.FadeOut(300, Easing.OutQuint); + pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight }; + base.OnFocusLost(state); + } + + protected override bool OnClick(InputState state) + { + if (bindTarget != null) bindTarget.IsBinding = false; + bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); + if (bindTarget != null) bindTarget.IsBinding = true; + + return bindTarget != null; + } + + private class KeyButton : Container + { + public readonly Framework.Input.Bindings.KeyBinding KeyBinding; + + private readonly Box box; + public readonly OsuSpriteText Text; + + private Color4 hoverColour; + + private bool isBinding; + + public bool IsBinding + { + get { return isBinding; } + set + { + if (value == isBinding) return; + isBinding = value; + + updateHoverState(); + } + } + + public KeyButton(Framework.Input.Bindings.KeyBinding keyBinding) + { + KeyBinding = keyBinding; + + Margin = new MarginPadding(padding); + + // todo: use this in a meaningful way + // var isDefault = keyBinding.Action is Enum; + + Masking = true; + CornerRadius = padding; + + Height = height; + AutoSizeAxes = Axes.X; + + Children = new Drawable[] + { + new Container + { + AlwaysPresent = true, + Width = 80, + Height = height, + }, + box = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + Text = new OsuSpriteText + { + Font = "Venera", + TextSize = 10, + Margin = new MarginPadding(5), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = keyBinding.KeyCombination.ReadableString(), + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.YellowDark; + } + + protected override bool OnHover(InputState state) + { + updateHoverState(); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + updateHoverState(); + base.OnHoverLost(state); + } + + private void updateHoverState() + { + if (isBinding) + { + box.FadeColour(Color4.White, transition_time, Easing.OutQuint); + Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint); + } + else + { + box.FadeColour(IsHovered ? hoverColour : Color4.Black, transition_time, Easing.OutQuint); + Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint); + } + } + + public void UpdateKeyCombination(params Key[] newCombination) + { + KeyBinding.KeyCombination = newCombination; + Text.Text = KeyBinding.KeyCombination.ReadableString(); + } + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSection.cs new file mode 100644 index 0000000000..19baeca771 --- /dev/null +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSection.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Game.Input; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; +using OpenTK; + +namespace osu.Game.Overlays.KeyBinding +{ + public abstract class KeyBindingsSection : SettingsSection + { + protected IEnumerable Defaults; + + protected RulesetInfo Ruleset; + + protected KeyBindingsSection() + { + FlowContent.Spacing = new Vector2(0, 1); + } + + [BackgroundDependencyLoader] + private void load(KeyBindingStore store) + { + var enumType = Defaults?.FirstOrDefault()?.Action?.GetType(); + + if (enumType == null) return; + + // for now let's just assume a variant of zero. + // this will need to be implemented in a better way in the future. + int? variant = null; + if (Ruleset != null) + variant = 0; + + var bindings = store.Query(Ruleset?.ID, variant); + + foreach (Enum v in Enum.GetValues(enumType)) + // one row per valid action. + Add(new KeyBindingRow(v, bindings.Where(b => b.Action.Equals((int)(object)v)))); + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs new file mode 100644 index 0000000000..20941115e3 --- /dev/null +++ b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.KeyBinding +{ + public class RulesetBindingsSection : KeyBindingsSection + { + public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override string Header => Ruleset.Name; + + public RulesetBindingsSection(RulesetInfo ruleset) + { + Ruleset = ruleset; + + Defaults = ruleset.CreateInstance().GetDefaultKeyBindings(); + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs new file mode 100644 index 0000000000..7d6ef7ffa6 --- /dev/null +++ b/osu.Game/Overlays/KeyBindingOverlay.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.KeyBinding; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays +{ + public class KeyBindingOverlay : SettingsOverlay + { + protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!"); + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(RulesetStore rulesets, GlobalKeyBindingInputManager global) + { + AddSection(new GlobalKeyBindingsSection(global, "Global")); + + foreach (var ruleset in rulesets.Query()) + AddSection(new RulesetBindingsSection(ruleset)); + } + + public KeyBindingOverlay() + : base(false) + { + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index 95e0fef9aa..1bce31c789 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays settingsSection.Bounding = true; this.FadeIn(transition_time, Easing.OutQuint); - InputManager.ChangeFocus(settingsSection); + GetContainingInputManager().ChangeFocus(settingsSection); } protected override void PopOut() diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs new file mode 100644 index 0000000000..b4d9cac045 --- /dev/null +++ b/osu.Game/Overlays/MainSettings.cs @@ -0,0 +1,151 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; +using osu.Game.Screens.Ranking; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays +{ + public class MainSettings : SettingsOverlay + { + private readonly KeyBindingOverlay keyBindingOverlay; + private BackButton backButton; + + protected override IEnumerable CreateSections() => new SettingsSection[] + { + new GeneralSection(), + new GraphicsSection(), + new GameplaySection(), + new AudioSection(), + new SkinSection(), + new InputSection(keyBindingOverlay), + new OnlineSection(), + new MaintenanceSection(), + new DebugSection(), + }; + + protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); + protected override Drawable CreateFooter() => new SettingsFooter(); + + public MainSettings() + : base(true) + { + keyBindingOverlay = new KeyBindingOverlay + { + Depth = 1, + Anchor = Anchor.TopRight, + }; + keyBindingOverlay.StateChanged += keyBindingOverlay_StateChanged; + } + + public override bool AcceptsFocus => keyBindingOverlay.State != Visibility.Visible; + + private const float hidden_width = 120; + + private void keyBindingOverlay_StateChanged(VisibilityContainer container, Visibility visibility) + { + switch (visibility) + { + case Visibility.Visible: + Background.FadeTo(0.9f, 300, Easing.OutQuint); + Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); + + SectionsContainer.FadeOut(300, Easing.OutQuint); + ContentContainer.MoveToX(hidden_width - WIDTH, 500, Easing.OutQuint); + + backButton.Delay(100).FadeIn(100); + break; + case Visibility.Hidden: + Background.FadeTo(0.6f, 500, Easing.OutQuint); + Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); + + SectionsContainer.FadeIn(500, Easing.OutQuint); + ContentContainer.MoveToX(0, 500, Easing.OutQuint); + + backButton.FadeOut(100); + break; + } + } + + protected override float ExpandedPosition => keyBindingOverlay.State == Visibility.Visible ? hidden_width - WIDTH : base.ExpandedPosition; + + [BackgroundDependencyLoader] + private void load() + { + ContentContainer.Add(keyBindingOverlay); + + ContentContainer.Add(backButton = new BackButton + { + Alpha = 0, + Width = hidden_width, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Action = () => keyBindingOverlay.Hide() + }); + } + + private class BackButton : OsuClickableContainer + { + private AspectContainer aspect; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + aspect = new AspectContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -15, + Size = new Vector2(15), + Shadow = true, + Icon = FontAwesome.fa_chevron_left + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 15, + TextSize = 12, + Font = @"Exo2.0-Bold", + Text = @"back", + }, + } + } + }; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + aspect.ScaleTo(1, 1000, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + } + } +} diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 942633b35e..83e92c5554 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Input; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -31,12 +30,10 @@ namespace osu.Game.Overlays.Music private readonly Bindable beatmapBacking = new Bindable(); public IEnumerable BeatmapSets; - private InputManager inputManager; [BackgroundDependencyLoader] - private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours, UserInputManager inputManager) + private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours) { - this.inputManager = inputManager; this.beatmaps = beatmaps; Children = new Drawable[] @@ -102,7 +99,7 @@ namespace osu.Game.Overlays.Music protected override void PopIn() { filter.Search.HoldFocus = true; - Schedule(() => inputManager.ChangeFocus(filter.Search)); + Schedule(() => GetContainingInputManager().ChangeFocus(filter.Search)); this.ResizeTo(new Vector2(1, playlist_height), transition_duration, Easing.OutQuint); this.FadeIn(transition_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 746b6dd50f..dcab942522 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -258,7 +258,10 @@ namespace osu.Game.Overlays Type = EdgeEffectType.Glow, Radius = 8, }; + } + protected override void LoadComplete() + { updateGlow(); FinishTransforms(true); } diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index c5b8b0cf85..d1d40388e1 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -103,7 +103,7 @@ namespace osu.Game.Overlays.SearchableList protected override void OnFocus(InputState state) { - InputManager.ChangeFocus(Filter.Search); + GetContainingInputManager().ChangeFocus(Filter.Search); } protected override void PopIn() diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index 47f7abf571..9b1c5e5e49 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -26,8 +26,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { base.Dispose(isDisposing); - audio.OnNewDevice -= onDeviceChanged; - audio.OnLostDevice -= onDeviceChanged; + if (audio != null) + { + audio.OnNewDevice -= onDeviceChanged; + audio.OnLostDevice -= onDeviceChanged; + } } private void updateItems() diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index d07f156673..7ae45159d9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -57,12 +57,9 @@ namespace osu.Game.Overlays.Settings.Sections.General Spacing = new Vector2(0f, 5f); } - private InputManager inputManager; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, APIAccess api, UserInputManager inputManager) + private void load(OsuColour colours, APIAccess api) { - this.inputManager = inputManager; this.colours = colours; api?.Register(this); @@ -174,7 +171,7 @@ namespace osu.Game.Overlays.Settings.Sections.General break; } - if (form != null) inputManager.ChangeFocus(form); + if (form != null) GetContainingInputManager()?.ChangeFocus(form); } public override bool AcceptsFocus => true; @@ -183,7 +180,7 @@ namespace osu.Game.Overlays.Settings.Sections.General protected override void OnFocus(InputState state) { - if (form != null) inputManager.ChangeFocus(form); + if (form != null) GetContainingInputManager().ChangeFocus(form); base.OnFocus(state); } @@ -192,7 +189,6 @@ namespace osu.Game.Overlays.Settings.Sections.General private TextBox username; private TextBox password; private APIAccess api; - private InputManager inputManager; private void performLogin() { @@ -201,9 +197,8 @@ namespace osu.Game.Overlays.Settings.Sections.General } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, OsuConfigManager config, UserInputManager inputManager) + private void load(APIAccess api, OsuConfigManager config) { - this.inputManager = inputManager; this.api = api; Direction = FillDirection.Vertical; Spacing = new Vector2(0, 5); @@ -256,7 +251,7 @@ namespace osu.Game.Overlays.Settings.Sections.General protected override void OnFocus(InputState state) { - Schedule(() => { inputManager.ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); + Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs index 9ead4ca7a4..01e73d0168 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs @@ -10,14 +10,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input { protected override string Header => "Keyboard"; - public KeyboardSettings() + public KeyboardSettings(KeyBindingOverlay keyConfig) { Children = new Drawable[] { new OsuButton { RelativeSizeAxes = Axes.X, - Text = "Key Configuration" + Text = "Key Configuration", + Action = () => keyConfig.ToggleVisibility() }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 65df3746b3..5ece8aad77 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -12,12 +12,12 @@ namespace osu.Game.Overlays.Settings.Sections public override string Header => "Input"; public override FontAwesome Icon => FontAwesome.fa_keyboard_o; - public InputSection() + public InputSection(KeyBindingOverlay keyConfig) { Children = new Drawable[] { new MouseSettings(), - new KeyboardSettings(), + new KeyboardSettings(keyConfig), }; } } diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index c554b54a87..80d60b6e5d 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -11,6 +11,15 @@ namespace osu.Game.Overlays.Settings { public class SettingsHeader : Container { + private readonly string heading; + private readonly string subheading; + + public SettingsHeader(string heading, string subheading) + { + this.heading = heading; + this.subheading = subheading; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -28,7 +37,7 @@ namespace osu.Game.Overlays.Settings { new OsuSpriteText { - Text = "settings", + Text = heading, TextSize = 40, Margin = new MarginPadding { @@ -39,7 +48,7 @@ namespace osu.Game.Overlays.Settings new OsuSpriteText { Colour = colours.Pink, - Text = "Change the way osu! behaves", + Text = subheading, TextSize = 18, Margin = new MarginPadding { diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 68ebde6b28..f091192d27 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System.Collections.Generic; @@ -25,25 +24,38 @@ namespace osu.Game.Overlays.Settings public IEnumerable FilterableChildren => Children.OfType(); public string[] FilterTerms => new[] { Header }; + + private const int header_size = 26; + private const int header_margin = 25; + private const int border_size = 2; + public bool MatchingFilter { - set - { - this.FadeTo(value ? 1 : 0); - } + set { this.FadeTo(value ? 1 : 0); } } - private readonly SpriteText headerLabel; - protected SettingsSection() { Margin = new MarginPadding { Top = 20 }; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - const int header_size = 26; - const int header_margin = 25; - const int border_size = 2; + FlowContent = new FillFlowContainer + { + Margin = new MarginPadding + { + Top = header_size + header_margin + }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 30), + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { AddRangeInternal(new Drawable[] { new Box @@ -65,28 +77,16 @@ namespace osu.Game.Overlays.Settings AutoSizeAxes = Axes.Y, Children = new[] { - headerLabel = new OsuSpriteText + new OsuSpriteText { TextSize = header_size, Text = Header, + Colour = colours.Yellow }, - FlowContent = new FillFlowContainer - { - Margin = new MarginPadding { Top = header_size + header_margin }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 30), - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }, + FlowContent } }, }); } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - headerLabel.Colour = colours.Yellow; - } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 6eafc65d12..b22a5ab126 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -14,12 +14,12 @@ using osu.Game.Overlays.Toolbar; namespace osu.Game.Overlays.Settings { - public class Sidebar : Container, IStateful + public class Sidebar : Container, IStateful { - private readonly FillFlowContainer content; + private readonly FillFlowContainer content; internal const float DEFAULT_WIDTH = ToolbarButton.WIDTH; internal const int EXPANDED_WIDTH = 200; - protected override Container Content => content; + protected override Container Content => content; public Sidebar() { @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings { Children = new[] { - content = new FillFlowContainer + content = new FillFlowContainer { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, @@ -87,6 +87,8 @@ namespace osu.Game.Overlays.Settings get { return state; } set { + expandEvent?.Cancel(); + if (state == value) return; state = value; diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 1dcabbfa15..f9ec2f68b5 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -2,9 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; +using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -12,11 +15,10 @@ using osu.Framework.Input; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; -using osu.Game.Overlays.Settings.Sections; namespace osu.Game.Overlays { - public class SettingsOverlay : OsuFocusedOverlayContainer + public abstract class SettingsOverlay : OsuFocusedOverlayContainer { internal const float CONTENT_MARGINS = 10; @@ -24,125 +26,152 @@ namespace osu.Game.Overlays public const float SIDEBAR_WIDTH = Sidebar.DEFAULT_WIDTH; - private const float width = 400; + protected const float WIDTH = 400; private const float sidebar_padding = 10; - private Sidebar sidebar; - private SidebarButton[] sidebarButtons; + protected Container ContentContainer; + + protected override Container Content => ContentContainer; + + protected Sidebar Sidebar; private SidebarButton selectedSidebarButton; - private SettingsSectionsContainer sectionsContainer; + protected SettingsSectionsContainer SectionsContainer; private SearchTextBox searchTextBox; - private Func getToolbarHeight; + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; - public SettingsOverlay() + private readonly bool showSidebar; + + protected Box Background; + + protected SettingsOverlay(bool showSidebar) { + this.showSidebar = showSidebar; RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; } - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuGame game) + protected virtual IEnumerable CreateSections() => null; + + [BackgroundDependencyLoader] + private void load() { - var sections = new SettingsSection[] + InternalChild = ContentContainer = new Container { - new GeneralSection(), - new GraphicsSection(), - new GameplaySection(), - new AudioSection(), - new SkinSection(), - new InputSection(), - new OnlineSection(), - new MaintenanceSection(), - new DebugSection(), - }; - Children = new Drawable[] - { - new Box + Width = WIDTH, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, - sectionsContainer = new SettingsSectionsContainer - { - RelativeSizeAxes = Axes.Y, - Width = width, - Margin = new MarginPadding { Left = SIDEBAR_WIDTH }, - ExpandableHeader = new SettingsHeader(), - FixedHeader = searchTextBox = new SearchTextBox + Background = new Box { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Width = 0.95f, - Margin = new MarginPadding - { - Top = 20, - Bottom = 20 - }, - Exit = Hide, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Scale = new Vector2(2, 1), // over-extend to the left for transitions + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, }, - Children = sections, - Footer = new SettingsFooter() - }, - sidebar = new Sidebar - { - Width = SIDEBAR_WIDTH, - Children = sidebarButtons = sections.Select(section => - new SidebarButton + SectionsContainer = new SettingsSectionsContainer + { + RelativeSizeAxes = Axes.Both, + ExpandableHeader = CreateHeader(), + FixedHeader = searchTextBox = new SearchTextBox { - Section = section, - Action = s => + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Width = 0.95f, + Margin = new MarginPadding { - sectionsContainer.ScrollTo(s); - sidebar.State = ExpandedState.Contracted; + Top = 20, + Bottom = 20 }, - } - ).ToArray() + Exit = Hide, + }, + Footer = CreateFooter() + }, } }; - selectedSidebarButton = sidebarButtons[0]; - selectedSidebarButton.Selected = true; - - sectionsContainer.SelectedSection.ValueChanged += section => + if (showSidebar) { - selectedSidebarButton.Selected = false; - selectedSidebarButton = sidebarButtons.Single(b => b.Section == section); - selectedSidebarButton.Selected = true; - }; + AddInternal(Sidebar = new Sidebar { Width = SIDEBAR_WIDTH }); - searchTextBox.Current.ValueChanged += newValue => sectionsContainer.SearchContainer.SearchTerm = newValue; + SectionsContainer.SelectedSection.ValueChanged += section => + { + selectedSidebarButton.Selected = false; + selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section); + selectedSidebarButton.Selected = true; + }; + } - getToolbarHeight = () => game?.ToolbarOffset ?? 0; + searchTextBox.Current.ValueChanged += newValue => SectionsContainer.SearchContainer.SearchTerm = newValue; + + CreateSections()?.ForEach(AddSection); } + protected void AddSection(SettingsSection section) + { + SectionsContainer.Add(section); + + if (Sidebar != null) + { + var button = new SidebarButton + { + Section = section, + Action = s => + { + SectionsContainer.ScrollTo(s); + Sidebar.State = ExpandedState.Contracted; + }, + }; + + Sidebar.Add(button); + + if (selectedSidebarButton == null) + { + selectedSidebarButton = Sidebar.Children.First(); + selectedSidebarButton.Selected = true; + } + } + } + + protected virtual Drawable CreateHeader() => new Container(); + + protected virtual Drawable CreateFooter() => new Container(); + protected override void PopIn() { base.PopIn(); - sectionsContainer.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - sidebar.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH / 2); + ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); searchTextBox.HoldFocus = true; } + protected virtual float ExpandedPosition => 0; + protected override void PopOut() { base.PopOut(); - sectionsContainer.MoveToX(-width, TRANSITION_LENGTH, Easing.OutQuint); - sidebar.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH / 2); + ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - InputManager.ChangeFocus(null); + GetContainingInputManager().ChangeFocus(null); } public override bool AcceptsFocus => true; @@ -151,7 +180,7 @@ namespace osu.Game.Overlays protected override void OnFocus(InputState state) { - InputManager.ChangeFocus(searchTextBox); + GetContainingInputManager().ChangeFocus(searchTextBox); base.OnFocus(state); } @@ -159,11 +188,11 @@ namespace osu.Game.Overlays { base.UpdateAfterChildren(); - sectionsContainer.Margin = new MarginPadding { Left = sidebar.DrawWidth }; - sectionsContainer.Padding = new MarginPadding { Top = getToolbarHeight() }; + ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; + ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } - private class SettingsSectionsContainer : SectionsContainer + protected class SettingsSectionsContainer : SectionsContainer { public SearchContainer SearchContainer; diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 1ecfe90aa1..32344ca7eb 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -22,8 +22,6 @@ namespace osu.Game.Overlays.Toolbar private readonly ToolbarUserArea userArea; - protected override bool HideOnEscape => false; - protected override bool BlockPassThroughMouse => false; private const double transition_time = 500; @@ -60,8 +58,8 @@ namespace osu.Game.Overlays.Toolbar AutoSizeAxes = Axes.X, Children = new Drawable[] { - new ToolbarSocialButton(), new ToolbarChatButton(), + new ToolbarSocialButton(), new ToolbarMusicButton(), new ToolbarButton { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index a4c319291c..b5e3f837fc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -19,147 +19,155 @@ namespace osu.Game.Rulesets.Objects.Legacy { public override HitObject Parse(string text) { - string[] split = text.Split(','); - ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax; - bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); - type &= ~ConvertHitObjectType.NewCombo; - - var soundType = (LegacySoundType)int.Parse(split[4]); - var bankInfo = new SampleBankInfo(); - - HitObject result = null; - - if ((type & ConvertHitObjectType.Circle) > 0) + try { - result = CreateHit(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo); + string[] split = text.Split(','); - if (split.Length > 5) - readCustomSampleBanks(split[5], bankInfo); - } - else if ((type & ConvertHitObjectType.Slider) > 0) - { - CurveType curveType = CurveType.Catmull; - double length = 0; - var points = new List { new Vector2(int.Parse(split[0]), int.Parse(split[1])) }; + ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax; + bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); + type &= ~ConvertHitObjectType.NewCombo; - string[] pointsplit = split[5].Split('|'); - foreach (string t in pointsplit) + var soundType = (LegacySoundType)int.Parse(split[4]); + var bankInfo = new SampleBankInfo(); + + HitObject result = null; + + if ((type & ConvertHitObjectType.Circle) > 0) { - if (t.Length == 1) + result = CreateHit(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo); + + if (split.Length > 5) + readCustomSampleBanks(split[5], bankInfo); + } + else if ((type & ConvertHitObjectType.Slider) > 0) + { + CurveType curveType = CurveType.Catmull; + double length = 0; + var points = new List { new Vector2(int.Parse(split[0]), int.Parse(split[1])) }; + + string[] pointsplit = split[5].Split('|'); + foreach (string t in pointsplit) { - switch (t) + if (t.Length == 1) { - case @"C": - curveType = CurveType.Catmull; - break; - case @"B": - curveType = CurveType.Bezier; - break; - case @"L": - curveType = CurveType.Linear; - break; - case @"P": - curveType = CurveType.PerfectCurve; - break; + switch (t) + { + case @"C": + curveType = CurveType.Catmull; + break; + case @"B": + curveType = CurveType.Bezier; + break; + case @"L": + curveType = CurveType.Linear; + break; + case @"P": + curveType = CurveType.PerfectCurve; + break; + } + continue; } - continue; + + string[] temp = t.Split(':'); + points.Add(new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture))); } - string[] temp = t.Split(':'); - points.Add(new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture))); - } + int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); - int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); + if (repeatCount > 9000) + throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); - if (repeatCount > 9000) - throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); + if (split.Length > 7) + length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); - if (split.Length > 7) - length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); + if (split.Length > 10) + readCustomSampleBanks(split[10], bankInfo); - if (split.Length > 10) - readCustomSampleBanks(split[10], bankInfo); + // One node for each repeat + the start and end nodes + // Note that the first length of the slider is considered a repeat, but there are no actual repeats happening + int nodes = Math.Max(0, repeatCount - 1) + 2; - // One node for each repeat + the start and end nodes - // Note that the first length of the slider is considered a repeat, but there are no actual repeats happening - int nodes = Math.Max(0, repeatCount - 1) + 2; - - // Populate node sample bank infos with the default hit object sample bank - var nodeBankInfos = new List(); - for (int i = 0; i < nodes; i++) - nodeBankInfos.Add(bankInfo.Clone()); - - // Read any per-node sample banks - if (split.Length > 9 && split[9].Length > 0) - { - string[] sets = split[9].Split('|'); + // Populate node sample bank infos with the default hit object sample bank + var nodeBankInfos = new List(); for (int i = 0; i < nodes; i++) + nodeBankInfos.Add(bankInfo.Clone()); + + // Read any per-node sample banks + if (split.Length > 9 && split[9].Length > 0) { - if (i >= sets.Length) - break; + string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) + { + if (i >= sets.Length) + break; - SampleBankInfo info = nodeBankInfos[i]; - readCustomSampleBanks(sets[i], info); + SampleBankInfo info = nodeBankInfos[i]; + readCustomSampleBanks(sets[i], info); + } } - } - // Populate node sound types with the default hit object sound type - var nodeSoundTypes = new List(); - for (int i = 0; i < nodes; i++) - nodeSoundTypes.Add(soundType); - - // Read any per-node sound types - if (split.Length > 8 && split[8].Length > 0) - { - string[] adds = split[8].Split('|'); + // Populate node sound types with the default hit object sound type + var nodeSoundTypes = new List(); for (int i = 0; i < nodes; i++) + nodeSoundTypes.Add(soundType); + + // Read any per-node sound types + if (split.Length > 8 && split[8].Length > 0) { - if (i >= adds.Length) - break; + string[] adds = split[8].Split('|'); + for (int i = 0; i < nodes; i++) + { + if (i >= adds.Length) + break; - int sound; - int.TryParse(adds[i], out sound); - nodeSoundTypes[i] = (LegacySoundType)sound; + int sound; + int.TryParse(adds[i], out sound); + nodeSoundTypes[i] = (LegacySoundType)sound; + } } + + // Generate the final per-node samples + var nodeSamples = new List(nodes); + for (int i = 0; i <= repeatCount; i++) + nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); + + result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples); } - - // Generate the final per-node samples - var nodeSamples = new List(nodes); - for (int i = 0; i <= repeatCount; i++) - nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - - result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples); - } - else if ((type & ConvertHitObjectType.Spinner) > 0) - { - result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture)); - - if (split.Length > 6) - readCustomSampleBanks(split[6], bankInfo); - } - else if ((type & ConvertHitObjectType.Hold) > 0) - { - // Note: Hold is generated by BMS converts - - double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); - - if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) + else if ((type & ConvertHitObjectType.Spinner) > 0) { - string[] ss = split[5].Split(':'); - endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture); - readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture)); + + if (split.Length > 6) + readCustomSampleBanks(split[6], bankInfo); + } + else if ((type & ConvertHitObjectType.Hold) > 0) + { + // Note: Hold is generated by BMS converts + + double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + + if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) + { + string[] ss = split[5].Split(':'); + endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture); + readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + } + + result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime); } - result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime); + if (result == null) + throw new InvalidOperationException($@"Unknown hit object type {type}."); + + result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + result.Samples = convertSoundType(soundType, bankInfo); + + return result; + } + catch (FormatException) + { + throw new FormatException("One or more hit objects were malformed."); } - - if (result == null) - throw new InvalidOperationException($@"Unknown hit object type {type}."); - - result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); - result.Samples = convertSoundType(soundType, bankInfo); - - return result; } private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index ba040403ba..e5fa7117f3 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Scoring; using osu.Game.Overlays.Settings; @@ -53,5 +54,17 @@ namespace osu.Game.Rulesets /// Do not override this unless you are a legacy mode. /// public virtual int LegacyID => -1; + + /// + /// A list of available variant ids. + /// + public virtual IEnumerable AvailableVariants => new[] { 0 }; + + /// + /// Get a list of default keys for the specified variant. + /// + /// A variant. + /// A list of valid s. + public virtual IEnumerable GetDefaultKeyBindings(int variant = 0) => new KeyBinding[] { }; } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index bc23649ede..740369b1b6 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -6,7 +6,7 @@ using SQLite.Net.Attributes; namespace osu.Game.Rulesets { - public class RulesetInfo + public class RulesetInfo : IEquatable { [PrimaryKey, AutoIncrement] public int? ID { get; set; } @@ -21,5 +21,7 @@ namespace osu.Game.Rulesets public bool Available { get; set; } public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); + + public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 387fa15191..8512d4b5e1 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.UI internal RulesetContainer(Ruleset ruleset) { Ruleset = ruleset; - KeyConversionInputManager = CreateActionMappingInputManager(); + KeyConversionInputManager = CreateKeyBindingInputManager(); KeyConversionInputManager.RelativeSizeAxes = Axes.Both; } @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.UI /// Creates a key conversion input manager. /// /// The input manager. - protected virtual PassThroughInputManager CreateActionMappingInputManager() => new PassThroughInputManager(); + public virtual PassThroughInputManager CreateKeyBindingInputManager() => new PassThroughInputManager(); protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new FramedReplayInputHandler(replay); diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index c5bbf04075..5b07ca6020 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens } // Make sure the in-progress loading is complete before pushing the screen. - while (screen.LoadState < LoadState.Loaded) + while (screen.LoadState < LoadState.Ready) Thread.Sleep(1); base.Push(screen); diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 01e20fdbd7..63c29a8f78 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Play } } - public override bool HandleInput => receptor?.IsAlive != true; + public override bool HandleInput => receptor == null; private Receptor receptor; diff --git a/osu.Game/Screens/Play/MenuOverlay.cs b/osu.Game/Screens/Play/MenuOverlay.cs index a0f867d248..0a8e172e57 100644 --- a/osu.Game/Screens/Play/MenuOverlay.cs +++ b/osu.Game/Screens/Play/MenuOverlay.cs @@ -22,8 +22,6 @@ namespace osu.Game.Screens.Play private const int button_height = 70; private const float background_alpha = 0.75f; - protected override bool HideOnEscape => false; - protected override bool BlockPassThroughKeyboard => true; public Action OnRetry; @@ -95,7 +93,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Height = button_height, - Action = delegate { + Action = delegate + { action?.Invoke(); Hide(); } diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipButton.cs index a67cf4e5ea..3cf371f1e1 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipButton.cs @@ -227,9 +227,9 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Horizontal, Children = new[] { - new SpriteIcon { Icon = FontAwesome.fa_chevron_right }, - new SpriteIcon { Icon = FontAwesome.fa_chevron_right }, - new SpriteIcon { Icon = FontAwesome.fa_chevron_right }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, } }, new OsuSpriteText diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index c513daf3d9..68f0cb3661 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -18,8 +18,6 @@ namespace osu.Game.Screens.Play { private const int bottom_bar_height = 5; - protected override bool HideOnEscape => false; - private static readonly Vector2 handle_size = new Vector2(14, 25); private const float transition_duration = 200; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 1fb9c707f0..872f7483b6 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -50,8 +50,6 @@ namespace osu.Game.Screens.Select AlwaysPresent = true; } - protected override bool HideOnEscape => false; - protected override bool BlockPassThroughMouse => false; protected override void PopIn() diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 838e6f7123..e83613125b 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -153,7 +153,7 @@ namespace osu.Game.Screens.Select { searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - inputManager.ChangeFocus(searchTextBox); + GetContainingInputManager().ChangeFocus(searchTextBox); } public void Activate() @@ -163,13 +163,9 @@ namespace osu.Game.Screens.Select private readonly Bindable ruleset = new Bindable(); - private InputManager inputManager; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, OsuGame osu, UserInputManager inputManager) + private void load(OsuColour colours, OsuGame osu) { - this.inputManager = inputManager; - sortTabs.AccentColour = colours.GreenLight; if (osu != null) diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index ac3b0b5c3b..d7c85fff90 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -373,6 +373,7 @@ namespace osu.Game.Screens.Select.Leaderboards Icon = FontAwesome.fa_square, Colour = OsuColour.FromHex(@"3087ac"), Rotation = 45, + Size = new Vector2(20), Shadow = true, }, new SpriteIcon @@ -380,7 +381,7 @@ namespace osu.Game.Screens.Select.Leaderboards Origin = Anchor.Centre, Icon = icon, Colour = OsuColour.FromHex(@"a4edff"), - Scale = new Vector2(0.8f), + Size = new Vector2(14), }, new GlowingSpriteText(value, @"Exo2.0-Bold", 17, Color4.White, OsuColour.FromHex(@"83ccfa")) { diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 0a410f0e0f..c66cc7beff 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select.Options /// Colour of the button. /// Icon of the button. /// Hotkey of the button. - /// Action the button does. + /// Binding the button does. /// /// Lower depth to be put on the left, and higher to be put on the right. /// Notice this is different to ! diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f8423a90ed..e20ddf1c09 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -156,11 +156,11 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours, UserInputManager input) + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) { if (Footer != null) { - Footer.AddButton(@"random", colours.Green, () => triggerRandom(input), Key.F2); + Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"options", colours.Blue, BeatmapOptions.ToggleVisibility, Key.F3); BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, promptDelete, Key.Number4, float.MaxValue); @@ -253,8 +253,6 @@ namespace osu.Game.Screens.Select } else { - Ruleset.Value = beatmap.Ruleset; - if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID) sampleChangeDifficulty.Play(); else @@ -267,9 +265,9 @@ namespace osu.Game.Screens.Select } } - private void triggerRandom(UserInputManager input) + private void triggerRandom() { - if (input.CurrentState.Keyboard.ShiftPressed) + if (GetContainingInputManager().CurrentState.Keyboard.ShiftPressed) carousel.SelectPreviousRandom(); else carousel.SelectNextRandom(); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index aba4384b59..07ab58bffc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -92,14 +92,22 @@ - - + + + + + + + + + + @@ -120,7 +128,6 @@ - @@ -142,8 +149,6 @@ - -