From 59b2f028289521f6eef68456662ca9255913287d Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 8 Oct 2019 18:34:09 +0800 Subject: [PATCH 01/48] initial implementation of customizable mods --- osu.Game/Overlays/Mods/ModControlSection.cs | 56 ++++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 58 +++++++++++++++++++++ osu.Game/Rulesets/Mods/IModHasSettings.cs | 15 ++++++ osu.Game/Rulesets/Mods/Mod.cs | 2 +- 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Mods/ModControlSection.cs create mode 100644 osu.Game/Rulesets/Mods/IModHasSettings.cs diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs new file mode 100644 index 0000000000..df6f4d15b0 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModControlSection.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModControlSection : Container + { + protected FillFlowContainer FlowContent; + protected override Container Content => FlowContent; + + public readonly Mod Mod; + + public ModControlSection(Mod mod) + { + Mod = mod; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + FlowContent = new FillFlowContainer + { + Margin = new MarginPadding { Top = 30 }, + Spacing = new Vector2(0, 5), + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + + if (Mod is IModHasSettings modHasSettings) + AddRange(modHasSettings.CreateControls()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddRangeInternal(new Drawable[] + { + new OsuSpriteText + { + Text = Mod.Name, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Colour = colours.Yellow, + }, + FlowContent + }); + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9ff320841a..5493080964 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -31,6 +31,7 @@ namespace osu.Game.Overlays.Mods public class ModSelectOverlay : WaveOverlayContainer { protected readonly TriangleButton DeselectAllButton; + protected readonly TriangleButton CustomizeButton; protected readonly TriangleButton CloseButton; protected readonly OsuSpriteText MultiplierLabel; @@ -42,6 +43,10 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; + protected readonly FillFlowContainer ModSettingsContent; + + protected readonly Container ModSettingsContainer; + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); protected readonly IBindable Ruleset = new Bindable(); @@ -226,6 +231,16 @@ namespace osu.Game.Overlays.Mods Right = 20 } }, + CustomizeButton = new TriangleButton + { + Width = 180, + Text = "Customization", + Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1, + Margin = new MarginPadding + { + Right = 20 + } + }, CloseButton = new TriangleButton { Width = 180, @@ -271,6 +286,36 @@ namespace osu.Game.Overlays.Mods }, }, }, + ModSettingsContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Width = 0.25f, + Alpha = 0, + X = -100, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0, 0, 0, 192) + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = ModSettingsContent = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Padding = new MarginPadding(20), + } + } + } + } }; } @@ -284,10 +329,23 @@ namespace osu.Game.Overlays.Mods Ruleset.BindTo(ruleset); if (mods != null) SelectedMods.BindTo(mods); + SelectedMods.ValueChanged += updateModSettings; + sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); } + private void updateModSettings(ValueChangedEvent> selectedMods) + { + var added = selectedMods.NewValue.Except(selectedMods.OldValue).FirstOrDefault(); + var removed = selectedMods.OldValue.Except(selectedMods.NewValue).FirstOrDefault(); + + if (added is IModHasSettings) + ModSettingsContent.Add(new ModControlSection(added)); + else if (removed is IModHasSettings) + ModSettingsContent.Remove(ModSettingsContent.Children.Where(section => section.Mod == removed).FirstOrDefault()); + } + public void DeselectAll() { foreach (var section in ModSectionsContainer.Children) diff --git a/osu.Game/Rulesets/Mods/IModHasSettings.cs b/osu.Game/Rulesets/Mods/IModHasSettings.cs new file mode 100644 index 0000000000..004279f71d --- /dev/null +++ b/osu.Game/Rulesets/Mods/IModHasSettings.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that allows user control over it's properties. + /// + public interface IModHasSettings + { + Drawable[] CreateControls(); + } +} diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 023d37497a..1c280c820d 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mods /// /// Creates a copy of this initialised to a default state. /// - public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); + public virtual Mod CreateCopy() => (Mod)MemberwiseClone(); public bool Equals(IMod other) => GetType() == other?.GetType(); } From 9375ef5eeae16c551b9e950becbb148a1a782dfd Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 8 Oct 2019 19:42:15 +0800 Subject: [PATCH 02/48] clear settings controls when changing rulesets --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5493080964..a344bcc254 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -330,6 +330,7 @@ namespace osu.Game.Overlays.Mods if (mods != null) SelectedMods.BindTo(mods); SelectedMods.ValueChanged += updateModSettings; + Ruleset.ValueChanged += _ => ModSettingsContent.Clear(); sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); From 2bc6932567548187d6696f4900ae98cfd1eaddc9 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 5 Nov 2019 00:55:55 +0800 Subject: [PATCH 03/48] make interface mod applicable --- osu.Game/Rulesets/Mods/IModHasSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/IModHasSettings.cs b/osu.Game/Rulesets/Mods/IModHasSettings.cs index 004279f71d..b5058de82b 100644 --- a/osu.Game/Rulesets/Mods/IModHasSettings.cs +++ b/osu.Game/Rulesets/Mods/IModHasSettings.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for mods that allows user control over it's properties. /// - public interface IModHasSettings + public interface IModHasSettings : IApplicableMod { Drawable[] CreateControls(); } From a92b32f6dc94a164e5a2502e5f070240673f555e Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 5 Nov 2019 00:56:09 +0800 Subject: [PATCH 04/48] add basic tests --- .../TestSceneModCustomizationSettings.cs | 108 ++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 +- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs new file mode 100644 index 0000000000..ec5b3c1e16 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModCustomizationSettings : OsuTestScene + { + private TestModSelectOverlay modSelect; + + [BackgroundDependencyLoader] + private void load() + { + Add(modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }); + + var testMod = new TestModCustomizable(); + + AddStep("open", modSelect.Show); + AddAssert("button disabled", () => !modSelect.CustomizeButton.Enabled.Value); + AddStep("select mod", () => modSelect.SelectMod(testMod)); + AddAssert("button enabled", () => modSelect.CustomizeButton.Enabled.Value); + AddStep("open customization", () => modSelect.CustomizeButton.Click()); + AddAssert("controls exist", () => modSelect.GetControlSection(testMod) != null); + AddStep("deselect mod", () => modSelect.SelectMod(testMod)); + AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); + } + + private class TestModSelectOverlay : ModSelectOverlay + { + public new Container ModSettingsContainer => base.ModSettingsContainer; + public new TriangleButton CustomizeButton => base.CustomizeButton; + + public void SelectMod(Mod mod) => + ModSectionsContainer.Children.Single((s) => s.ModType == mod.Type) + .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + + public ModControlSection GetControlSection(Mod mod) => + ModSettingsContent.Children.FirstOrDefault((s) => s.Mod == mod); + + protected override void LoadComplete() + { + base.LoadComplete(); + + foreach (var section in ModSectionsContainer) + if (section.ModType == ModType.Conversion) + section.Mods = new Mod[] { new TestModCustomizable() }; + else + section.Mods = new Mod[] { }; + } + } + + private class TestModCustomizable : Mod, IModHasSettings + { + public override string Name => "Customizable Mod"; + + public override string Acronym => "CM"; + + public override double ScoreMultiplier => 1.0; + + public override ModType Type => ModType.Conversion; + + public readonly BindableFloat sliderBindable = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + }; + + public readonly BindableBool tickBindable = new BindableBool(); + + public Drawable[] CreateControls() + { + BindableFloat sliderControl = new BindableFloat(); + BindableBool tickControl = new BindableBool(); + + sliderControl.BindTo(sliderBindable); + tickControl.BindTo(tickBindable); + + return new Drawable[] + { + new SettingsSlider + { + LabelText = "Slider", + Bindable = sliderControl + }, + new SettingsCheckbox + { + LabelText = "Checkbox", + Bindable = tickControl + } + }; + } + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a344bcc254..fc5ed1b3f3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -331,6 +331,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.ValueChanged += updateModSettings; Ruleset.ValueChanged += _ => ModSettingsContent.Clear(); + CustomizeButton.Enabled.Value = false; sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); @@ -344,7 +345,12 @@ namespace osu.Game.Overlays.Mods if (added is IModHasSettings) ModSettingsContent.Add(new ModControlSection(added)); else if (removed is IModHasSettings) - ModSettingsContent.Remove(ModSettingsContent.Children.Where(section => section.Mod == removed).FirstOrDefault()); + ModSettingsContent.Remove(ModSettingsContent.Children.Where(section => section.Mod == removed).Single()); + + CustomizeButton.Enabled.Value = ModSettingsContent.Children.Count > 0; + + if (ModSettingsContainer.Alpha == 1 && ModSettingsContent.Children.Count == 0) + ModSettingsContainer.Alpha = 0; } public void DeselectAll() From 15e85234e4d4b80df421bb1c9d8383b35c5aab10 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 15 Nov 2019 11:12:51 +0800 Subject: [PATCH 05/48] remove unecessary step --- .../Visual/UserInterface/TestSceneModCustomizationSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs index ec5b3c1e16..83d77cccdb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs @@ -34,7 +34,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("select mod", () => modSelect.SelectMod(testMod)); AddAssert("button enabled", () => modSelect.CustomizeButton.Enabled.Value); AddStep("open customization", () => modSelect.CustomizeButton.Click()); - AddAssert("controls exist", () => modSelect.GetControlSection(testMod) != null); AddStep("deselect mod", () => modSelect.SelectMod(testMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); } From c4515429158bb5ed4a7a14b10b65f218fd8cd481 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 30 Nov 2019 03:01:07 +0300 Subject: [PATCH 06/48] Rankings overlay basic implementation --- .../Visual/Online/TestSceneRankingsOverlay.cs | 59 ++++++ osu.Game/Overlays/RankingsOverlay.cs | 198 ++++++++++++++++++ osu.Game/Users/Drawables/UpdateableFlag.cs | 14 ++ 3 files changed, 271 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs create mode 100644 osu.Game/Overlays/RankingsOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs new file mode 100644 index 0000000000..1f08fe7530 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Overlays.Rankings.Tables; +using osu.Framework.Allocation; +using osu.Game.Overlays; +using NUnit.Framework; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsOverlay : OsuTestScene + { + protected override bool UseOnlineAPI => true; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PerformanceTable), + typeof(ScoresTable), + typeof(CountriesTable), + typeof(TableRowBackground), + typeof(UserBasedTable), + typeof(RankingsTable<>), + typeof(RankingsOverlay) + }; + + [Cached] + private RankingsOverlay rankingsOverlay; + + public TestSceneRankingsOverlay() + { + Add(rankingsOverlay = new RankingsOverlay()); + } + + [Test] + public void TestShow() + { + AddStep("Show", rankingsOverlay.Show); + } + + [Test] + public void TestShowCountry() + { + AddStep("Show US", () => rankingsOverlay.ShowCountry(new Country + { + FlagName = "US", + FullName = "United States" + })); + } + + [Test] + public void TestHide() + { + AddStep("Hide", rankingsOverlay.Hide); + } + } +} diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs new file mode 100644 index 0000000000..b9b2fe7232 --- /dev/null +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -0,0 +1,198 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Overlays.Rankings; +using osu.Game.Users; +using osu.Game.Rulesets; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using System.Threading; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Rankings.Tables; + +namespace osu.Game.Overlays +{ + public class RankingsOverlay : FullscreenOverlay + { + private readonly Bindable country = new Bindable(); + private readonly Bindable scope = new Bindable(); + private readonly Bindable ruleset = new Bindable(); + + private readonly BasicScrollContainer scrollFlow; + private readonly Box background; + private readonly Container contentPlaceholder; + private readonly DimmedLoadingLayer loading; + + private APIRequest request; + private CancellationTokenSource cancellationToken; + + [Resolved] + private IAPIProvider api { get; set; } + + public RankingsOverlay() + { + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + scrollFlow = new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new RankingsHeader + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Country = { BindTarget = country }, + Scope = { BindTarget = scope }, + Ruleset = { BindTarget = ruleset } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + contentPlaceholder = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Vertical = 10 } + }, + loading = new DimmedLoadingLayer(), + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + Waves.FirstWaveColour = colour.Green; + Waves.SecondWaveColour = colour.GreenLight; + Waves.ThirdWaveColour = colour.GreenDark; + Waves.FourthWaveColour = colour.GreenDarker; + + background.Colour = OsuColour.Gray(0.1f); + } + + protected override void LoadComplete() + { + country.BindValueChanged(_ => redraw(), true); + scope.BindValueChanged(_ => redraw(), true); + ruleset.BindValueChanged(_ => redraw(), true); + base.LoadComplete(); + } + + public void ShowCountry(Country requested) + { + if (requested == null) + return; + + Show(); + + if (country.Value?.FlagName == requested.FlagName) + return; + + country.Value = requested; + } + + private void redraw() + { + scrollFlow.ScrollToStart(); + + loading.Show(); + + cancellationToken?.Cancel(); + request?.Cancel(); + + cancellationToken = new CancellationTokenSource(); + + switch (scope.Value) + { + default: + contentPlaceholder.Clear(); + loading.Hide(); + return; + + case RankingsScope.Performance: + createPerformanceTable(); + return; + + case RankingsScope.Country: + createCountryTable(); + return; + + case RankingsScope.Score: + createScoreTable(); + return; + } + } + + private void createCountryTable() + { + request = new GetCountryRankingsRequest(ruleset.Value); + ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new CountriesTable(1, rankings.Countries); + loadTable(table); + }); + + api.Queue(request); + } + + private void createPerformanceTable() + { + request = new GetUserRankingsRequest(ruleset.Value, country: country.Value?.FlagName); + ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new PerformanceTable(1, rankings.Users); + loadTable(table); + }); + + api.Queue(request); + } + + private void createScoreTable() + { + request = new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); + ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new ScoresTable(1, rankings.Users); + loadTable(table); + }); + + api.Queue(request); + } + + private void loadTable(Drawable table) + { + LoadComponentAsync(table, t => + { + contentPlaceholder.Clear(); + contentPlaceholder.Add(t); + loading.Hide(); + }, cancellationToken.Token); + } + } +} diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index abc16b2390..e7c23d3c23 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Overlays; namespace osu.Game.Users.Drawables { @@ -34,5 +37,16 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, }; } + + [Resolved(canBeNull: true)] + private RankingsOverlay rankingsOverlay { get; set; } + + protected override bool OnClick(ClickEvent e) + { + if (Country != null) + rankingsOverlay?.ShowCountry(Country); + + return true; + } } } From 0ac46755468b9bf840c816707674a626ae7e95c6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 1 Dec 2019 03:52:41 +0300 Subject: [PATCH 07/48] Implement IEquatable --- osu.Game/Overlays/RankingsOverlay.cs | 3 --- osu.Game/Users/Country.cs | 5 ++++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index b9b2fe7232..d3586a538b 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -111,9 +111,6 @@ namespace osu.Game.Overlays Show(); - if (country.Value?.FlagName == requested.FlagName) - return; - country.Value = requested; } diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index 1dcce6e870..101d268a60 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -1,11 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using Newtonsoft.Json; namespace osu.Game.Users { - public class Country + public class Country : IEquatable { /// /// The name of this country. @@ -18,5 +19,7 @@ namespace osu.Game.Users /// [JsonProperty(@"code")] public string FlagName; + + public bool Equals(Country other) => FlagName == other.FlagName; } } From f375db368f1ac0208d7dd4dd5497da93ec6ab448 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 1 Dec 2019 03:56:03 +0300 Subject: [PATCH 08/48] Remove useless null check --- osu.Game/Users/Drawables/UpdateableFlag.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index e7c23d3c23..1d30720889 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -43,9 +43,7 @@ namespace osu.Game.Users.Drawables protected override bool OnClick(ClickEvent e) { - if (Country != null) - rankingsOverlay?.ShowCountry(Country); - + rankingsOverlay?.ShowCountry(Country); return true; } } From 62daea195c5ab9b90d170c878c5fbaba5161e360 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 1 Dec 2019 04:09:45 +0300 Subject: [PATCH 09/48] Fix possible null --- osu.Game/Users/Country.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index 101d268a60..a9fcd69286 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -20,6 +20,6 @@ namespace osu.Game.Users [JsonProperty(@"code")] public string FlagName; - public bool Equals(Country other) => FlagName == other.FlagName; + public bool Equals(Country other) => FlagName == other?.FlagName; } } From 48732e49b94967317181137d08f2cbc642f54b9f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 5 Dec 2019 04:20:13 +0300 Subject: [PATCH 10/48] Improve async logic --- osu.Game/Overlays/RankingsOverlay.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index d3586a538b..948d165b82 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Online.API; using System.Threading; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; +using osu.Framework.Threading; namespace osu.Game.Overlays { @@ -30,6 +31,7 @@ namespace osu.Game.Overlays private readonly DimmedLoadingLayer loading; private APIRequest request; + private ScheduledDelegate showTableDelegate; private CancellationTokenSource cancellationToken; [Resolved] @@ -120,11 +122,10 @@ namespace osu.Game.Overlays loading.Show(); + showTableDelegate?.Cancel(); cancellationToken?.Cancel(); request?.Cancel(); - cancellationToken = new CancellationTokenSource(); - switch (scope.Value) { default: @@ -184,12 +185,12 @@ namespace osu.Game.Overlays private void loadTable(Drawable table) { - LoadComponentAsync(table, t => + showTableDelegate = Schedule(() => LoadComponentAsync(table, t => { contentPlaceholder.Clear(); contentPlaceholder.Add(t); loading.Hide(); - }, cancellationToken.Token); + }, (cancellationToken = new CancellationTokenSource()).Token)); } } } From f8f144b6c04671f0daf7155791e7314e5cdf26e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 5 Dec 2019 05:20:22 +0300 Subject: [PATCH 11/48] Remove pointless ScheduledDelegate --- osu.Game/Overlays/RankingsOverlay.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 948d165b82..0ec03ebcc9 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API; using System.Threading; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; -using osu.Framework.Threading; namespace osu.Game.Overlays { @@ -31,7 +30,6 @@ namespace osu.Game.Overlays private readonly DimmedLoadingLayer loading; private APIRequest request; - private ScheduledDelegate showTableDelegate; private CancellationTokenSource cancellationToken; [Resolved] @@ -122,7 +120,6 @@ namespace osu.Game.Overlays loading.Show(); - showTableDelegate?.Cancel(); cancellationToken?.Cancel(); request?.Cancel(); @@ -185,12 +182,12 @@ namespace osu.Game.Overlays private void loadTable(Drawable table) { - showTableDelegate = Schedule(() => LoadComponentAsync(table, t => + LoadComponentAsync(table, t => { contentPlaceholder.Clear(); contentPlaceholder.Add(t); loading.Hide(); - }, (cancellationToken = new CancellationTokenSource()).Token)); + }, (cancellationToken = new CancellationTokenSource()).Token); } } } From 5f9b9631ef38bae53c320a9aa1512250399b1f47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:07:39 +0900 Subject: [PATCH 12/48] Move scope/country restrictions into RankingsOverlay --- osu.Game/Overlays/Rankings/HeaderTitle.cs | 10 +--------- osu.Game/Overlays/RankingsOverlay.cs | 24 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs index a1a893fa6b..b08a2a3900 100644 --- a/osu.Game/Overlays/Rankings/HeaderTitle.cs +++ b/osu.Game/Overlays/Rankings/HeaderTitle.cs @@ -74,13 +74,7 @@ namespace osu.Game.Overlays.Rankings base.LoadComplete(); } - private void onScopeChanged(ValueChangedEvent scope) - { - scopeText.Text = scope.NewValue.ToString(); - - if (scope.NewValue != RankingsScope.Performance) - Country.Value = null; - } + private void onScopeChanged(ValueChangedEvent scope) => scopeText.Text = scope.NewValue.ToString(); private void onCountryChanged(ValueChangedEvent country) { @@ -90,8 +84,6 @@ namespace osu.Game.Overlays.Rankings return; } - Scope.Value = RankingsScope.Performance; - flag.Country = country.NewValue; flag.Show(); } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 0ec03ebcc9..b989e9473b 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -98,9 +98,25 @@ namespace osu.Game.Overlays protected override void LoadComplete() { - country.BindValueChanged(_ => redraw(), true); - scope.BindValueChanged(_ => redraw(), true); - ruleset.BindValueChanged(_ => redraw(), true); + country.BindValueChanged(_ => + { + // if a country is requested, force performance scope. + if (country.Value != null) + scope.Value = RankingsScope.Performance; + + Scheduler.AddOnce(loadNewContent); + }, true); + scope.BindValueChanged(_ => + { + // country filtering is only valid for performance scope. + if (scope.Value != RankingsScope.Performance) + country.Value = null; + + Scheduler.AddOnce(loadNewContent); + }, true); + + ruleset.BindValueChanged(_ => Scheduler.AddOnce(loadNewContent), true); + base.LoadComplete(); } @@ -114,7 +130,7 @@ namespace osu.Game.Overlays country.Value = requested; } - private void redraw() + private void loadNewContent() { scrollFlow.ScrollToStart(); From cd473f207a1dab9f058cc1e370508fdc63e3a0a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:09:33 +0900 Subject: [PATCH 13/48] Use child set, not Clear/Add --- osu.Game/Overlays/RankingsOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index b989e9473b..154cf0e47b 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -200,8 +200,7 @@ namespace osu.Game.Overlays { LoadComponentAsync(table, t => { - contentPlaceholder.Clear(); - contentPlaceholder.Add(t); + contentPlaceholder.Child = t; loading.Hide(); }, (cancellationToken = new CancellationTokenSource()).Token); } From 6e9157d59c75cf680c11d216f68cd42b233b9dc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:26:36 +0900 Subject: [PATCH 14/48] Standardise request/response handling --- .../API/Requests/GetUserRankingsRequest.cs | 7 +- osu.Game/Overlays/RankingsOverlay.cs | 97 ++++++++++--------- 2 files changed, 55 insertions(+), 49 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index 9c3eba9fdc..6f657aee8d 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -8,13 +8,14 @@ namespace osu.Game.Online.API.Requests { public class GetUserRankingsRequest : GetRankingsRequest { + public readonly UserRankingsType Type; + private readonly string country; - private readonly UserRankingsType type; public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null) : base(ruleset, page) { - this.type = type; + this.Type = type; this.country = country; } @@ -28,7 +29,7 @@ namespace osu.Game.Online.API.Requests return req; } - protected override string TargetPostfix() => type.ToString().ToLowerInvariant(); + protected override string TargetPostfix() => Type.ToString().ToLowerInvariant(); } public enum UserRankingsType diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 154cf0e47b..aa42b029e0 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -26,10 +26,10 @@ namespace osu.Game.Overlays private readonly BasicScrollContainer scrollFlow; private readonly Box background; - private readonly Container contentPlaceholder; + private readonly Container tableContainer; private readonly DimmedLoadingLayer loading; - private APIRequest request; + private APIRequest lastRequest; private CancellationTokenSource cancellationToken; [Resolved] @@ -68,7 +68,7 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Children = new Drawable[] { - contentPlaceholder = new Container + tableContainer = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -132,76 +132,81 @@ namespace osu.Game.Overlays private void loadNewContent() { - scrollFlow.ScrollToStart(); - loading.Show(); cancellationToken?.Cancel(); - request?.Cancel(); + lastRequest?.Cancel(); + var request = createScopedRequest(); + lastRequest = request; + + if (request == null) + { + loadTable(null); + return; + } + + request.Success += () => loadTable(createTableFromResponse(request)); + request.Failure += _ => loadTable(null); + + api.Queue(request); + } + + private APIRequest createScopedRequest() + { switch (scope.Value) { - default: - contentPlaceholder.Clear(); - loading.Hide(); - return; - case RankingsScope.Performance: - createPerformanceTable(); - return; + return new GetUserRankingsRequest(ruleset.Value, country: country.Value?.FlagName); case RankingsScope.Country: - createCountryTable(); - return; + return new GetCountryRankingsRequest(ruleset.Value); case RankingsScope.Score: - createScoreTable(); - return; + return new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); } + + return null; } - private void createCountryTable() + private Drawable createTableFromResponse(APIRequest request) { - request = new GetCountryRankingsRequest(ruleset.Value); - ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => + switch (request) { - var table = new CountriesTable(1, rankings.Countries); - loadTable(table); - }); + case GetUserRankingsRequest userRequest: + switch (userRequest.Type) + { + case UserRankingsType.Performance: + return new PerformanceTable(1, userRequest.Result.Users); - api.Queue(request); - } + case UserRankingsType.Score: + return new ScoresTable(1, userRequest.Result.Users); + } - private void createPerformanceTable() - { - request = new GetUserRankingsRequest(ruleset.Value, country: country.Value?.FlagName); - ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new PerformanceTable(1, rankings.Users); - loadTable(table); - }); + return null; - api.Queue(request); - } + case GetCountryRankingsRequest countryRequest: + return new CountriesTable(1, countryRequest.Result.Countries); + } - private void createScoreTable() - { - request = new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); - ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new ScoresTable(1, rankings.Users); - loadTable(table); - }); - - api.Queue(request); + return null; } private void loadTable(Drawable table) { + scrollFlow.ScrollToStart(); + + if (table == null) + { + tableContainer.Clear(); + loading.Hide(); + return; + } + LoadComponentAsync(table, t => { - contentPlaceholder.Child = t; loading.Hide(); + tableContainer.Child = table; }, (cancellationToken = new CancellationTokenSource()).Token); } } From de413418c7404f7c34fcb75ef16a6fac942ae5ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:50:12 +0900 Subject: [PATCH 15/48] Remove redundant prefix --- osu.Game/Online/API/Requests/GetUserRankingsRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index 6f657aee8d..143d21e40d 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.API.Requests public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null) : base(ruleset, page) { - this.Type = type; + Type = type; this.country = country; } From 33737d3d89b7381b29f695c5bfc6312464c74ff2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 5 Dec 2019 09:53:25 +0300 Subject: [PATCH 16/48] Move tests to TestSceneRankingsOverlay due to refactoring --- .../Visual/Online/TestSceneRankingsHeader.cs | 2 - .../Online/TestSceneRankingsHeaderTitle.cs | 5 --- .../Visual/Online/TestSceneRankingsOverlay.cs | 39 ++++++++++++++++--- osu.Game/Overlays/RankingsOverlay.cs | 26 ++++++------- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index c0da605cdb..e708934bc3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -68,9 +68,7 @@ namespace osu.Game.Tests.Visual.Online }; AddStep("Set country", () => countryBindable.Value = country); - AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddAssert("Check country is Null", () => countryBindable.Value == null); AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs index 849ca2defc..0edf104da0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs @@ -43,11 +43,6 @@ namespace osu.Game.Tests.Visual.Online FullName = "United States" }; - AddStep("Set country", () => countryBindable.Value = countryA); - AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); - AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddAssert("Check country is Null", () => countryBindable.Value == null); - AddStep("Set country 1", () => countryBindable.Value = countryA); AddStep("Set country 2", () => countryBindable.Value = countryB); AddStep("Set null country", () => countryBindable.Value = null); diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 1f08fe7530..09f008b308 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -8,6 +8,8 @@ using osu.Framework.Allocation; using osu.Game.Overlays; using NUnit.Framework; using osu.Game.Users; +using osu.Framework.Bindables; +using osu.Game.Overlays.Rankings; namespace osu.Game.Tests.Visual.Online { @@ -29,9 +31,16 @@ namespace osu.Game.Tests.Visual.Online [Cached] private RankingsOverlay rankingsOverlay; + private readonly Bindable countryBindable = new Bindable(); + private readonly Bindable scope = new Bindable(); + public TestSceneRankingsOverlay() { - Add(rankingsOverlay = new RankingsOverlay()); + Add(rankingsOverlay = new TestRankingsOverlay + { + Country = { BindTarget = countryBindable }, + Scope = { BindTarget = scope }, + }); } [Test] @@ -40,14 +49,19 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show", rankingsOverlay.Show); } + [Test] + public void TestFlagScopeDependency() + { + AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); + AddAssert("Check country is Null", () => countryBindable.Value == null); + AddStep("Set country", () => countryBindable.Value = us_country); + AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); + } + [Test] public void TestShowCountry() { - AddStep("Show US", () => rankingsOverlay.ShowCountry(new Country - { - FlagName = "US", - FullName = "United States" - })); + AddStep("Show US", () => rankingsOverlay.ShowCountry(us_country)); } [Test] @@ -55,5 +69,18 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Hide", rankingsOverlay.Hide); } + + private static Country us_country = new Country + { + FlagName = "US", + FullName = "United States" + }; + + private class TestRankingsOverlay : RankingsOverlay + { + public new Bindable Country => base.Country; + + public new Bindable Scope => base.Scope; + } } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index aa42b029e0..e7c8b94a10 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -20,8 +20,8 @@ namespace osu.Game.Overlays { public class RankingsOverlay : FullscreenOverlay { - private readonly Bindable country = new Bindable(); - private readonly Bindable scope = new Bindable(); + protected readonly Bindable Country = new Bindable(); + protected readonly Bindable Scope = new Bindable(); private readonly Bindable ruleset = new Bindable(); private readonly BasicScrollContainer scrollFlow; @@ -58,8 +58,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Country = { BindTarget = country }, - Scope = { BindTarget = scope }, + Country = { BindTarget = Country }, + Scope = { BindTarget = Scope }, Ruleset = { BindTarget = ruleset } }, new Container @@ -98,19 +98,19 @@ namespace osu.Game.Overlays protected override void LoadComplete() { - country.BindValueChanged(_ => + Country.BindValueChanged(_ => { // if a country is requested, force performance scope. - if (country.Value != null) - scope.Value = RankingsScope.Performance; + if (Country.Value != null) + Scope.Value = RankingsScope.Performance; Scheduler.AddOnce(loadNewContent); }, true); - scope.BindValueChanged(_ => + Scope.BindValueChanged(_ => { // country filtering is only valid for performance scope. - if (scope.Value != RankingsScope.Performance) - country.Value = null; + if (Scope.Value != RankingsScope.Performance) + Country.Value = null; Scheduler.AddOnce(loadNewContent); }, true); @@ -127,7 +127,7 @@ namespace osu.Game.Overlays Show(); - country.Value = requested; + Country.Value = requested; } private void loadNewContent() @@ -154,10 +154,10 @@ namespace osu.Game.Overlays private APIRequest createScopedRequest() { - switch (scope.Value) + switch (Scope.Value) { case RankingsScope.Performance: - return new GetUserRankingsRequest(ruleset.Value, country: country.Value?.FlagName); + return new GetUserRankingsRequest(ruleset.Value, country: Country.Value?.FlagName); case RankingsScope.Country: return new GetCountryRankingsRequest(ruleset.Value); From bf7c309d4c87c7e74f0bcca629c81976067d00dd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 5 Dec 2019 10:05:04 +0300 Subject: [PATCH 17/48] Make field readonly --- osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 09f008b308..568e36df4c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Hide", rankingsOverlay.Hide); } - private static Country us_country = new Country + private static readonly Country us_country = new Country { FlagName = "US", FullName = "United States" From af35df4077015c5b45ebb5b8f304e9bdc58702f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 15:42:11 +0900 Subject: [PATCH 18/48] Add multiple mod testing and update test code style --- .../TestSceneModCustomizationSettings.cs | 56 +++++++++++-------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs index 83d77cccdb..f789e60252 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.BottomCentre, }); - var testMod = new TestModCustomizable(); + var testMod = new TestModCustomizable1(); AddStep("open", modSelect.Show); AddAssert("button disabled", () => !modSelect.CustomizeButton.Enabled.Value); @@ -44,64 +44,74 @@ namespace osu.Game.Tests.Visual.UserInterface public new TriangleButton CustomizeButton => base.CustomizeButton; public void SelectMod(Mod mod) => - ModSectionsContainer.Children.Single((s) => s.ModType == mod.Type) - .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) + .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); public ModControlSection GetControlSection(Mod mod) => - ModSettingsContent.Children.FirstOrDefault((s) => s.Mod == mod); + ModSettingsContent.Children.FirstOrDefault(s => s.Mod == mod); protected override void LoadComplete() { base.LoadComplete(); foreach (var section in ModSectionsContainer) + { if (section.ModType == ModType.Conversion) - section.Mods = new Mod[] { new TestModCustomizable() }; + { + section.Mods = new Mod[] + { + new TestModCustomizable1(), + new TestModCustomizable2() + }; + } else section.Mods = new Mod[] { }; + } } } - private class TestModCustomizable : Mod, IModHasSettings + private class TestModCustomizable1 : TestModCustomizable { - public override string Name => "Customizable Mod"; + public override string Name => "Customizable Mod 1"; - public override string Acronym => "CM"; + public override string Acronym => "CM1"; + } + private class TestModCustomizable2 : TestModCustomizable + { + public override string Name => "Customizable Mod 2"; + + public override string Acronym => "CM2"; + } + + private abstract class TestModCustomizable : Mod, IModHasSettings + { public override double ScoreMultiplier => 1.0; public override ModType Type => ModType.Conversion; - public readonly BindableFloat sliderBindable = new BindableFloat + public readonly BindableFloat SliderBindable = new BindableFloat { MinValue = 0, MaxValue = 10, }; - public readonly BindableBool tickBindable = new BindableBool(); + public readonly BindableBool TickBindable = new BindableBool(); - public Drawable[] CreateControls() - { - BindableFloat sliderControl = new BindableFloat(); - BindableBool tickControl = new BindableBool(); - - sliderControl.BindTo(sliderBindable); - tickControl.BindTo(tickBindable); - - return new Drawable[] + public Drawable[] CreateControls() => + new Drawable[] { new SettingsSlider { LabelText = "Slider", - Bindable = sliderControl + Bindable = SliderBindable }, new SettingsCheckbox { LabelText = "Checkbox", - Bindable = tickControl + Bindable = TickBindable } }; - } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index fc5ed1b3f3..d7d1135e81 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -345,7 +345,7 @@ namespace osu.Game.Overlays.Mods if (added is IModHasSettings) ModSettingsContent.Add(new ModControlSection(added)); else if (removed is IModHasSettings) - ModSettingsContent.Remove(ModSettingsContent.Children.Where(section => section.Mod == removed).Single()); + ModSettingsContent.Remove(ModSettingsContent.Children.Single(section => section.Mod == removed)); CustomizeButton.Enabled.Value = ModSettingsContent.Children.Count > 0; From 23e47530c50a38bd42874f85ec4c698b967f9e86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 17:09:24 +0900 Subject: [PATCH 19/48] Add a method for getting settings UI components automatically from a target class --- .../Configuration/SettingSourceAttribute.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 osu.Game/Configuration/SettingSourceAttribute.cs diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs new file mode 100644 index 0000000000..fe582b1461 --- /dev/null +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JetBrains.Annotations; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Configuration +{ + /// + /// An attribute to mark a bindable as being exposed to the user via settings controls. + /// Can be used in conjunction with to automatically create UI controls. + /// + [MeansImplicitUse] + [AttributeUsage(AttributeTargets.Property)] + public class SettingSourceAttribute : Attribute + { + public string Label { get; } + + public string Description { get; } + + public SettingSourceAttribute(string label, string description = null) + { + Label = label ?? string.Empty; + Description = description ?? string.Empty; + } + } + + public static class SettingSourceExtensions + { + public static IEnumerable CreateSettingsControls(this object obj) + { + var configProperties = obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute(true) != null); + + foreach (var property in configProperties) + { + var attr = property.GetCustomAttribute(true); + + switch (property.GetValue(obj)) + { + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case Bindable bBool: + yield return new SettingsCheckbox + { + LabelText = attr.Label, + Bindable = bBool + }; + + break; + } + } + } + } +} From a5d5099868f2c7d1a8672dc266030065366592c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 17:09:48 +0900 Subject: [PATCH 20/48] Use SettingsSource for mod cusomisation --- .../TestSceneModCustomizationSettings.cs | 30 +++++-------------- osu.Game/Overlays/Mods/ModControlSection.cs | 6 ++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 24 ++++++++------- osu.Game/Rulesets/Mods/IModHasSettings.cs | 15 ---------- 4 files changed, 25 insertions(+), 50 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/IModHasSettings.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs index f789e60252..8227fe8f7a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs @@ -6,9 +6,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; namespace osu.Game.Tests.Visual.UserInterface @@ -47,9 +47,6 @@ namespace osu.Game.Tests.Visual.UserInterface ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); - public ModControlSection GetControlSection(Mod mod) => - ModSettingsContent.Children.FirstOrDefault(s => s.Mod == mod); - protected override void LoadComplete() { base.LoadComplete(); @@ -84,34 +81,23 @@ namespace osu.Game.Tests.Visual.UserInterface public override string Acronym => "CM2"; } - private abstract class TestModCustomizable : Mod, IModHasSettings + private abstract class TestModCustomizable : Mod, IApplicableMod { public override double ScoreMultiplier => 1.0; public override ModType Type => ModType.Conversion; - public readonly BindableFloat SliderBindable = new BindableFloat + [SettingSource("Sample float", "Change something for a mod")] + public BindableFloat SliderBindable { get; } = new BindableFloat { MinValue = 0, MaxValue = 10, + Default = 5, + Value = 7 }; - public readonly BindableBool TickBindable = new BindableBool(); - - public Drawable[] CreateControls() => - new Drawable[] - { - new SettingsSlider - { - LabelText = "Slider", - Bindable = SliderBindable - }, - new SettingsCheckbox - { - LabelText = "Checkbox", - Bindable = TickBindable - } - }; + [SettingSource("Sample bool", "Clicking this changes a setting")] + public BindableBool TickBindable { get; } = new BindableBool(); } } } diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs index df6f4d15b0..f4b588ddb3 100644 --- a/osu.Game/Overlays/Mods/ModControlSection.cs +++ b/osu.Game/Overlays/Mods/ModControlSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; @@ -34,8 +35,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, }; - if (Mod is IModHasSettings modHasSettings) - AddRange(modHasSettings.CreateControls()); + AddRange(Mod.CreateSettingsControls()); } [BackgroundDependencyLoader] @@ -53,4 +53,4 @@ namespace osu.Game.Overlays.Mods }); } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d7d1135e81..cfad0126eb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -236,6 +237,7 @@ namespace osu.Game.Overlays.Mods Width = 180, Text = "Customization", Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1, + Enabled = { Value = false }, Margin = new MarginPadding { Right = 20 @@ -331,7 +333,6 @@ namespace osu.Game.Overlays.Mods SelectedMods.ValueChanged += updateModSettings; Ruleset.ValueChanged += _ => ModSettingsContent.Clear(); - CustomizeButton.Enabled.Value = false; sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); @@ -339,18 +340,21 @@ namespace osu.Game.Overlays.Mods private void updateModSettings(ValueChangedEvent> selectedMods) { - var added = selectedMods.NewValue.Except(selectedMods.OldValue).FirstOrDefault(); - var removed = selectedMods.OldValue.Except(selectedMods.NewValue).FirstOrDefault(); + foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue)) + { + var controls = added.CreateSettingsControls().ToList(); + if (controls.Count > 0) + ModSettingsContent.Add(new ModControlSection(added) { Children = controls }); + } - if (added is IModHasSettings) - ModSettingsContent.Add(new ModControlSection(added)); - else if (removed is IModHasSettings) - ModSettingsContent.Remove(ModSettingsContent.Children.Single(section => section.Mod == removed)); + foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue)) + ModSettingsContent.RemoveAll(section => section.Mod == removed); - CustomizeButton.Enabled.Value = ModSettingsContent.Children.Count > 0; + bool hasSettings = ModSettingsContent.Children.Count > 0; + CustomizeButton.Enabled.Value = hasSettings; - if (ModSettingsContainer.Alpha == 1 && ModSettingsContent.Children.Count == 0) - ModSettingsContainer.Alpha = 0; + if (!hasSettings) + ModSettingsContainer.Hide(); } public void DeselectAll() diff --git a/osu.Game/Rulesets/Mods/IModHasSettings.cs b/osu.Game/Rulesets/Mods/IModHasSettings.cs deleted file mode 100644 index b5058de82b..0000000000 --- a/osu.Game/Rulesets/Mods/IModHasSettings.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for mods that allows user control over it's properties. - /// - public interface IModHasSettings : IApplicableMod - { - Drawable[] CreateControls(); - } -} From 46d055604a6f049735a6d2b0ed12d33ef5287dab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:57:11 +0900 Subject: [PATCH 21/48] Customize -> Customise --- ...ionSettings.cs => TestSceneModSettings.cs} | 26 +++++++++---------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 ++--- 2 files changed, 16 insertions(+), 16 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneModCustomizationSettings.cs => TestSceneModSettings.cs} (78%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs similarity index 78% rename from osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 8227fe8f7a..7a11ba5294 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneModCustomizationSettings : OsuTestScene + public class TestSceneModSettings : OsuTestScene { private TestModSelectOverlay modSelect; @@ -27,13 +27,13 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.BottomCentre, }); - var testMod = new TestModCustomizable1(); + var testMod = new TestModCustomisable1(); AddStep("open", modSelect.Show); - AddAssert("button disabled", () => !modSelect.CustomizeButton.Enabled.Value); + AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); AddStep("select mod", () => modSelect.SelectMod(testMod)); - AddAssert("button enabled", () => modSelect.CustomizeButton.Enabled.Value); - AddStep("open customization", () => modSelect.CustomizeButton.Click()); + AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); + AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); AddStep("deselect mod", () => modSelect.SelectMod(testMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); } @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestModSelectOverlay : ModSelectOverlay { public new Container ModSettingsContainer => base.ModSettingsContainer; - public new TriangleButton CustomizeButton => base.CustomizeButton; + public new TriangleButton CustomiseButton => base.CustomiseButton; public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) @@ -57,8 +57,8 @@ namespace osu.Game.Tests.Visual.UserInterface { section.Mods = new Mod[] { - new TestModCustomizable1(), - new TestModCustomizable2() + new TestModCustomisable1(), + new TestModCustomisable2() }; } else @@ -67,21 +67,21 @@ namespace osu.Game.Tests.Visual.UserInterface } } - private class TestModCustomizable1 : TestModCustomizable + private class TestModCustomisable1 : TestModCustomisable { - public override string Name => "Customizable Mod 1"; + public override string Name => "Customisable Mod 1"; public override string Acronym => "CM1"; } - private class TestModCustomizable2 : TestModCustomizable + private class TestModCustomisable2 : TestModCustomisable { - public override string Name => "Customizable Mod 2"; + public override string Name => "Customisable Mod 2"; public override string Acronym => "CM2"; } - private abstract class TestModCustomizable : Mod, IApplicableMod + private abstract class TestModCustomisable : Mod, IApplicableMod { public override double ScoreMultiplier => 1.0; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index cfad0126eb..e860463b23 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Mods public class ModSelectOverlay : WaveOverlayContainer { protected readonly TriangleButton DeselectAllButton; - protected readonly TriangleButton CustomizeButton; + protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; protected readonly OsuSpriteText MultiplierLabel; @@ -232,7 +232,7 @@ namespace osu.Game.Overlays.Mods Right = 20 } }, - CustomizeButton = new TriangleButton + CustomiseButton = new TriangleButton { Width = 180, Text = "Customization", @@ -351,7 +351,7 @@ namespace osu.Game.Overlays.Mods ModSettingsContent.RemoveAll(section => section.Mod == removed); bool hasSettings = ModSettingsContent.Children.Count > 0; - CustomizeButton.Enabled.Value = hasSettings; + CustomiseButton.Enabled.Value = hasSettings; if (!hasSettings) ModSettingsContainer.Hide(); From 347373a3ba3ef852dabd35306721ff689b47b0af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 19:04:55 +0900 Subject: [PATCH 22/48] Fix test failures --- .../UserInterface/TestSceneModSettings.cs | 3 +++ osu.Game/Overlays/Mods/ModSection.cs | 22 +++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 7a11ba5294..435dfd92be 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -31,6 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("open", modSelect.Show); AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); + AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded); AddStep("select mod", () => modSelect.SelectMod(testMod)); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); @@ -43,6 +44,8 @@ namespace osu.Game.Tests.Visual.UserInterface public new Container ModSettingsContainer => base.ModSettingsContainer; public new TriangleButton CustomiseButton => base.CustomiseButton; + public bool ButtonsLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); + public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 9c0a164ad6..c55d1d8f70 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -57,6 +57,15 @@ namespace osu.Game.Overlays.Mods }).ToArray(); modsLoadCts?.Cancel(); + + if (modContainers.Length == 0) + { + ModIconsLoaded = true; + headerLabel.Hide(); + Hide(); + return; + } + ModIconsLoaded = false; LoadComponentsAsync(modContainers, c => @@ -67,17 +76,8 @@ namespace osu.Game.Overlays.Mods buttons = modContainers.OfType().ToArray(); - if (value.Any()) - { - headerLabel.FadeIn(200); - this.FadeIn(200); - } - else - { - // transition here looks weird as mods instantly disappear. - headerLabel.Hide(); - Hide(); - } + headerLabel.FadeIn(200); + this.FadeIn(200); } } From f65866648e3e914a358ec4a1639d24d4862c01c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 20:14:56 +0900 Subject: [PATCH 23/48] Use Array.Empty --- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 435dfd92be..fc44c5f595 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -65,7 +66,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } else - section.Mods = new Mod[] { }; + section.Mods = Array.Empty(); } } } From 1e49078c526f2050a137f087cf0000f678f3484e Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 17:51:44 +0800 Subject: [PATCH 24/48] Add OsuCursorSprite --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs new file mode 100644 index 0000000000..56c04ce1fc --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class OsuCursorSprite : CompositeDrawable + { + public Drawable ExpandTarget { get; protected set; } + } +} From 22f2a4bed2284ab2fa81f7cfbb4a6a17f56ecb27 Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 17:53:16 +0800 Subject: [PATCH 25/48] Fix LegacyCursor's cursormiddle expanding --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 8 ++++---- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 18 +++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 05b38ae195..cf133f54ea 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -5,13 +5,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.UI.Cursor; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning { - public class LegacyCursor : CompositeDrawable + public class LegacyCursor : OsuCursorSprite { - private NonPlayfieldSprite cursor; private bool spin; public LegacyCursor() @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - cursor = new NonPlayfieldSprite + ExpandTarget = new NonPlayfieldSprite { Texture = skin.GetTexture("cursor"), Anchor = Anchor.Centre, @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void LoadComplete() { if (spin) - cursor.Spin(10000, RotationDirection.Clockwise); + ExpandTarget.Spin(10000, RotationDirection.Clockwise); } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 0aa8661fd3..4a67d23f57 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private bool cursorExpand; - private Container expandTarget; + private OsuCursorSprite cursorSprite; public OsuCursor() { @@ -37,17 +37,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [BackgroundDependencyLoader] private void load() { - InternalChild = expandTarget = new Container + SkinnableDrawable cursor; + InternalChild = new Container { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + Child = cursor = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, } }; + cursorSprite = cursor.Drawable as OsuCursorSprite; } private const float pressed_scale = 1.2f; @@ -57,12 +59,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { if (!cursorExpand) return; - expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + cursorSprite.ExpandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); } - public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + public void Contract() => cursorSprite.ExpandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); - private class DefaultCursor : CompositeDrawable + private class DefaultCursor : OsuCursorSprite { public DefaultCursor() { @@ -73,8 +75,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor InternalChildren = new Drawable[] { - new CircularContainer + ExpandTarget = new CircularContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, BorderThickness = size / 6, From af90b45c407a0cbad892224204e83e5c6cfe0778 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 20:49:59 +0900 Subject: [PATCH 26/48] Refactor path visualisers to use bindables --- .../Components/PathControlPointPiece.cs | 116 ++++++++++++------ .../Components/PathControlPointVisualiser.cs | 34 +++-- 2 files changed, 98 insertions(+), 52 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 4fe02135c4..38da5939b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Graphics; @@ -20,10 +22,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { - public Action RequestSelection; + public Action RequestSelection; public readonly BindableBool IsSelected = new BindableBool(); - public readonly int Index; + + public readonly PathControlPoint ControlPoint; private readonly Slider slider; private readonly Path path; @@ -36,10 +39,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved] private OsuColour colours { get; set; } - public PathControlPointPiece(Slider slider, int index) + private IBindable sliderPosition; + private IBindable pathVersion; + + public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) { this.slider = slider; - Index = index; + + ControlPoint = controlPoint; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; @@ -85,48 +92,41 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components }; } - protected override void Update() + protected override void LoadComplete() { - base.Update(); + base.LoadComplete(); - Position = slider.StackedPosition + slider.Path.ControlPoints[Index].Position.Value; + sliderPosition = slider.PositionBindable.GetBoundCopy(); + sliderPosition.BindValueChanged(_ => updateDisplay()); + pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion.BindValueChanged(_ => updateDisplay()); + + IsSelected.BindValueChanged(_ => updateMarkerDisplay()); + + updateDisplay(); + } + + private void updateDisplay() + { updateMarkerDisplay(); updateConnectingPath(); } - /// - /// Updates the state of the circular control point marker. - /// - private void updateMarkerDisplay() - { - markerRing.Alpha = IsSelected.Value ? 1 : 0; - - Color4 colour = slider.Path.ControlPoints[Index].Type.Value.HasValue ? colours.Red : colours.Yellow; - if (IsHovered || IsSelected.Value) - colour = Color4.White; - marker.Colour = colour; - } - - /// - /// Updates the path connecting this control point to the previous one. - /// - private void updateConnectingPath() - { - path.ClearVertices(); - - if (Index != slider.Path.ControlPoints.Count - 1) - { - path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[Index + 1].Position.Value - slider.Path.ControlPoints[Index].Position.Value); - } - - path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); - } - // The connecting path is excluded from positional input public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); + protected override bool OnHover(HoverEvent e) + { + updateMarkerDisplay(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateMarkerDisplay(); + } + protected override bool OnMouseDown(MouseDownEvent e) { if (RequestSelection == null) @@ -135,12 +135,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (e.Button) { case MouseButton.Left: - RequestSelection.Invoke(Index, e); + RequestSelection.Invoke(this, e); return true; case MouseButton.Right: if (!IsSelected.Value) - RequestSelection.Invoke(Index, e); + RequestSelection.Invoke(this, e); return false; // Allow context menu to show } @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnDrag(DragEvent e) { - if (Index == 0) + if (ControlPoint == slider.Path.ControlPoints[0]) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account (Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime); @@ -169,11 +169,47 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components slider.Path.ControlPoints[i].Position.Value -= movementDelta; } else - slider.Path.ControlPoints[Index].Position.Value += e.Delta; + ControlPoint.Position.Value += e.Delta; return true; } protected override bool OnDragEnd(DragEndEvent e) => true; + + /// + /// Updates the state of the circular control point marker. + /// + private void updateMarkerDisplay() + { + Position = slider.StackedPosition + ControlPoint.Position.Value; + + markerRing.Alpha = IsSelected.Value ? 1 : 0; + + Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow; + if (IsHovered || IsSelected.Value) + colour = Color4.White; + marker.Colour = colour; + } + + /// + /// Updates the path connecting this control point to the previous one. + /// + private void updateConnectingPath() + { + path.ClearVertices(); + + int index = slider.Path.ControlPoints.IndexOf(ControlPoint); + + if (index == -1) + return; + + if (++index != slider.Path.ControlPoints.Count) + { + path.AddVertex(Vector2.Zero); + path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value); + } + + path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 434e74ddeb..eb6e3c01e1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Humanizer; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -32,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IPlacementHandler placementHandler { get; set; } + private IBindableList controlPoints; + public PathControlPointVisualiser(Slider slider, bool allowSelection) { this.slider = slider; @@ -47,24 +50,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.LoadComplete(); inputManager = GetContainingInputManager(); + + controlPoints = slider.Path.ControlPoints.GetBoundCopy(); + controlPoints.ItemsAdded += addControlPoints; + controlPoints.ItemsRemoved += removeControlPoints; + + addControlPoints(controlPoints); } - protected override void Update() + private void addControlPoints(IEnumerable controlPoints) { - base.Update(); - - while (slider.Path.ControlPoints.Count > Pieces.Count) + foreach (var point in controlPoints) { - var piece = new PathControlPointPiece(slider, Pieces.Count); + var piece = new PathControlPointPiece(slider, point); if (allowSelection) piece.RequestSelection = selectPiece; Pieces.Add(piece); } + } - while (slider.Path.ControlPoints.Count < Pieces.Count) - Pieces.Remove(Pieces[Pieces.Count - 1]); + private void removeControlPoints(IEnumerable controlPoints) + { + foreach (var point in controlPoints) + Pieces.RemoveAll(p => p.ControlPoint == point); } protected override bool OnClick(ClickEvent e) @@ -87,20 +97,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; - private void selectPiece(int index, MouseButtonEvent e) + private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e) { if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) - Pieces[index].IsSelected.Toggle(); + piece.IsSelected.Toggle(); else { - foreach (var piece in Pieces) - piece.IsSelected.Value = piece.Index == index; + foreach (var p in Pieces) + p.IsSelected.Value = p == piece; } } private bool deleteSelected() { - List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => slider.Path.ControlPoints[p.Index]).ToList(); + List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList(); // Ensure that there are any points to be deleted if (toRemove.Count == 0) From 0ee303f7d6417130079bd7862938e78c403c0e19 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 22:45:44 +0900 Subject: [PATCH 27/48] Remove unused using --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 38da5939b6..7c66f38854 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -12,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Graphics; From 9b318d2869a99a97f7e213946b7993fac71cf972 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 00:07:07 +0900 Subject: [PATCH 28/48] Add right-click menu item to change path type --- .../Components/PathControlPointPiece.cs | 2 +- .../Components/PathControlPointVisualiser.cs | 45 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 7c66f38854..c2aefac587 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnHover(HoverEvent e) { updateMarkerDisplay(); - return true; + return false; } protected override void OnHoverLost(HoverLostEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index eb6e3c01e1..352bb56030 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -15,6 +16,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -161,9 +163,50 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return new MenuItem[] { - new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected()) + new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected()), + new OsuMenuItem("Type") + { + Items = new[] + { + createMenuItemForPathType(null), + createMenuItemForPathType(PathType.Linear), + createMenuItemForPathType(PathType.PerfectCurve), + createMenuItemForPathType(PathType.Catmull) + } + } }; } } + + private MenuItem createMenuItemForPathType(PathType? type) + { + int totalCount = Pieces.Count(p => p.IsSelected.Value); + int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type); + + var item = new PathTypeMenuItem(type, () => + { + foreach (var p in Pieces.Where(p => p.IsSelected.Value)) + p.ControlPoint.Type.Value = type; + }); + + if (countOfState == totalCount) + item.State.Value = TernaryState.True; + else if (countOfState > 0) + item.State.Value = TernaryState.Indeterminate; + else + item.State.Value = TernaryState.False; + + return item; + } + + private class PathTypeMenuItem : TernaryStateMenuItem + { + public PathTypeMenuItem(PathType? type, Action action) + : base(type == null ? "Inherit" : type.ToString().Humanize(), changeState, MenuItemType.Standard, _ => action?.Invoke()) + { + } + + private static TernaryState changeState(TernaryState state) => TernaryState.True; + } } } From 1b14b0e5b6cecdbfacbf44024f2ff0fa42252d93 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 00:08:38 +0900 Subject: [PATCH 29/48] Fix pieces blocking context menu --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 7c66f38854..c2aefac587 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnHover(HoverEvent e) { updateMarkerDisplay(); - return true; + return false; } protected override void OnHoverLost(HoverLostEvent e) From 94a298a82d5e965ff54e63e56b332c78af50f775 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 16:00:09 +0900 Subject: [PATCH 30/48] Refactor distance snap grid to not require hitobjects --- .../Edit/OsuDistanceSnapGrid.cs | 5 +- .../Editor/TestSceneDistanceSnapGrid.cs | 29 ++++++----- .../Components/CircularDistanceSnapGrid.cs | 25 +++++----- .../Compose/Components/DistanceSnapGrid.cs | 50 +++++++++---------- 4 files changed, 52 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 9b00204d51..bde86a2890 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -8,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject) - : base(hitObject, nextHitObject, hitObject.StackedEndPosition) + public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) + : base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime) { Masking = true; } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index e4c987923c..39b4bf7218 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual.Editor RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - new TestDistanceSnapGrid(new HitObject(), grid_position) + new TestDistanceSnapGrid() }; }); @@ -73,7 +72,7 @@ namespace osu.Game.Tests.Visual.Editor RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 }) + new TestDistanceSnapGrid(100) }; }); } @@ -82,68 +81,68 @@ namespace osu.Game.Tests.Visual.Editor { public new float DistanceSpacing => base.DistanceSpacing; - public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null) - : base(hitObject, nextHitObject, centrePosition) + public TestDistanceSnapGrid(double? endTime = null) + : base(grid_position, 0, endTime) { } - protected override void CreateContent(Vector2 centrePosition) + protected override void CreateContent(Vector2 startPosition) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5), - Position = centrePosition + Position = startPosition }); int beatIndex = 0; - for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) + for (float s = startPosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5, 10), - Position = new Vector2(s, centrePosition.Y), + Position = new Vector2(s, startPosition.Y), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) + for (float s = startPosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5, 10), - Position = new Vector2(s, centrePosition.Y), + Position = new Vector2(s, startPosition.Y), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) + for (float s = startPosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(10, 5), - Position = new Vector2(centrePosition.X, s), + Position = new Vector2(startPosition.X, s), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) + for (float s = startPosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(10, 5), - Position = new Vector2(centrePosition.X, s), + Position = new Vector2(startPosition.X, s), Colour = GetColourForBeatIndex(beatIndex) }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 91e19f9cc0..23ed10b92d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -5,19 +5,18 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularDistanceSnapGrid(HitObject hitObject, HitObject nextHitObject, Vector2 centrePosition) - : base(hitObject, nextHitObject, centrePosition) + protected CircularDistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null) + : base(startPosition, startTime, endTime) { } - protected override void CreateContent(Vector2 centrePosition) + protected override void CreateContent(Vector2 startPosition) { const float crosshair_thickness = 1; const float crosshair_max_size = 10; @@ -27,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components new Box { Origin = Anchor.Centre, - Position = centrePosition, + Position = startPosition, Width = crosshair_thickness, EdgeSmoothness = new Vector2(1), Height = Math.Min(crosshair_max_size, DistanceSpacing * 2), @@ -35,15 +34,15 @@ namespace osu.Game.Screens.Edit.Compose.Components new Box { Origin = Anchor.Centre, - Position = centrePosition, + Position = startPosition, EdgeSmoothness = new Vector2(1), Width = Math.Min(crosshair_max_size, DistanceSpacing * 2), Height = crosshair_thickness, } }); - float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); - float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); + float dx = Math.Max(startPosition.X, DrawWidth - startPosition.X); + float dy = Math.Max(startPosition.Y, DrawHeight - startPosition.Y); float maxDistance = new Vector2(dx, dy).Length; int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing)); @@ -54,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components AddInternal(new CircularProgress { Origin = Anchor.Centre, - Position = centrePosition, + Position = startPosition, Current = { Value = 1 }, Size = new Vector2(radius), InnerRadius = 4 * 1f / radius, @@ -66,9 +65,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public override (Vector2 position, double time) GetSnappedPosition(Vector2 position) { if (MaxIntervals == 0) - return (CentrePosition, StartTime); + return (StartPosition, StartTime); - Vector2 direction = position - CentrePosition; + Vector2 direction = position - StartPosition; if (direction == Vector2.Zero) direction = new Vector2(0.001f, 0.001f); @@ -78,9 +77,9 @@ namespace osu.Game.Screens.Edit.Compose.Components int radialCount = Math.Clamp((int)MathF.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); - Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; + Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius; - return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length)); + return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - StartPosition).Length)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9508a2cdf0..00326d04f7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Graphics; @@ -9,7 +8,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -24,21 +22,21 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected float DistanceSpacing { get; private set; } - /// - /// The snapping time at . - /// - protected double StartTime { get; private set; } - /// /// The maximum number of distance snapping intervals allowed. /// protected int MaxIntervals { get; private set; } /// - /// The position which the grid is centred on. - /// The first beat snapping tick is located at + in the desired direction. + /// The position which the grid should start. + /// The first beat snapping tick is located at + away from this point. /// - protected readonly Vector2 CentrePosition; + protected readonly Vector2 StartPosition; + + /// + /// The snapping time at . + /// + protected readonly double StartTime; [Resolved] protected OsuColour Colours { get; private set; } @@ -53,25 +51,23 @@ namespace osu.Game.Screens.Edit.Compose.Components private BindableBeatDivisor beatDivisor { get; set; } private readonly Cached gridCache = new Cached(); - private readonly HitObject hitObject; - private readonly HitObject nextHitObject; + private readonly double? endTime; - protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition) + /// + /// Creates a new . + /// + /// The position at which the grid should start. The first tick is located one distance spacing length away from this point. + /// The snapping time at . + /// The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded. + protected DistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null) { - this.hitObject = hitObject; - this.nextHitObject = nextHitObject; - - CentrePosition = centrePosition; + this.endTime = endTime; + StartPosition = startPosition; + StartTime = startTime; RelativeSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] - private void load() - { - StartTime = hitObject.GetEndTime(); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -83,12 +79,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); - if (nextHitObject == null) + if (endTime == null) MaxIntervals = int.MaxValue; else { // +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors - double maxDuration = nextHitObject.StartTime - StartTime + 1; + double maxDuration = endTime.Value - StartTime + 1; MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing)); } @@ -110,7 +106,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!gridCache.IsValid) { ClearInternal(); - CreateContent(CentrePosition); + CreateContent(StartPosition); gridCache.Validate(); } } @@ -118,7 +114,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Creates the content which visualises the grid ticks. /// - protected abstract void CreateContent(Vector2 centrePosition); + protected abstract void CreateContent(Vector2 startPosition); /// /// Snaps a position to this grid. From 48976f5d0e0e14663ac8b5d5c1f106c0fd592f1f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 9 Dec 2019 21:46:29 +0800 Subject: [PATCH 31/48] Add VS launcher profile for tournament client. --- osu.Desktop/Properties/launchSettings.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 osu.Desktop/Properties/launchSettings.json diff --git a/osu.Desktop/Properties/launchSettings.json b/osu.Desktop/Properties/launchSettings.json new file mode 100644 index 0000000000..5e768ec9fa --- /dev/null +++ b/osu.Desktop/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "osu! Desktop": { + "commandName": "Project" + }, + "osu! Tournament": { + "commandName": "Project", + "commandLineArgs": "--tournament" + } + } +} \ No newline at end of file From 06cde2b0c22feca53459df4213f46eb82ca6d0c1 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 10 Dec 2019 19:30:46 +0800 Subject: [PATCH 32/48] remove unused using directive --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index cf133f54ea..4e027c4351 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; using osuTK; From e37369304b7f75f67aafaa59947ca49b214a399e Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 10 Dec 2019 19:45:06 +0800 Subject: [PATCH 33/48] property-ize expand target --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 4a67d23f57..07cde5e407 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private bool cursorExpand; - private OsuCursorSprite cursorSprite; + private SkinnableDrawable cursorSprite; + + private Drawable ExpandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite; public OsuCursor() { @@ -37,19 +39,17 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [BackgroundDependencyLoader] private void load() { - SkinnableDrawable cursor; InternalChild = new Container { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = cursor = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + Child = cursorSprite = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, } }; - cursorSprite = cursor.Drawable as OsuCursorSprite; } private const float pressed_scale = 1.2f; @@ -59,10 +59,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { if (!cursorExpand) return; - cursorSprite.ExpandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + ExpandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); } - public void Contract() => cursorSprite.ExpandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + public void Contract() => ExpandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); private class DefaultCursor : OsuCursorSprite { From 1afeaf31bcad901bceec6766d3ec7a6810b5f82b Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 10 Dec 2019 19:58:56 +0800 Subject: [PATCH 34/48] make OsuCursorSprite abstract --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs index 56c04ce1fc..909c764b9e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.UI.Cursor { - public class OsuCursorSprite : CompositeDrawable + public abstract class OsuCursorSprite : CompositeDrawable { public Drawable ExpandTarget { get; protected set; } } From dbe46c6cf7df3dc77eb66bdfd88679d7d88add3c Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 10 Dec 2019 20:40:10 +0800 Subject: [PATCH 35/48] conform to coding styles --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 2 +- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 4e027c4351..02152fa51e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true; - InternalChildren = new Drawable[] + InternalChildren = new[] { new NonPlayfieldSprite { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 07cde5e407..4f3d07f208 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private SkinnableDrawable cursorSprite; - private Drawable ExpandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite; + private Drawable expandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite; public OsuCursor() { @@ -59,10 +59,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { if (!cursorExpand) return; - ExpandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); } - public void Contract() => ExpandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); private class DefaultCursor : OsuCursorSprite { @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Anchor = Anchor.Centre; Origin = Anchor.Centre; - InternalChildren = new Drawable[] + InternalChildren = new[] { ExpandTarget = new CircularContainer { From a37af311d010a1122d64ff5b8ca1c025a2ec3931 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 13:19:02 +0900 Subject: [PATCH 36/48] Simplify settings update logic --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 47 +++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e860463b23..ec5f99dc31 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -331,32 +331,10 @@ namespace osu.Game.Overlays.Mods Ruleset.BindTo(ruleset); if (mods != null) SelectedMods.BindTo(mods); - SelectedMods.ValueChanged += updateModSettings; - Ruleset.ValueChanged += _ => ModSettingsContent.Clear(); - sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); } - private void updateModSettings(ValueChangedEvent> selectedMods) - { - foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue)) - { - var controls = added.CreateSettingsControls().ToList(); - if (controls.Count > 0) - ModSettingsContent.Add(new ModControlSection(added) { Children = controls }); - } - - foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue)) - ModSettingsContent.RemoveAll(section => section.Mod == removed); - - bool hasSettings = ModSettingsContent.Children.Count > 0; - CustomiseButton.Enabled.Value = hasSettings; - - if (!hasSettings) - ModSettingsContainer.Hide(); - } - public void DeselectAll() { foreach (var section in ModSectionsContainer.Children) @@ -450,12 +428,14 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - private void selectedModsChanged(ValueChangedEvent> e) + private void selectedModsChanged(ValueChangedEvent> mods) { foreach (var section in ModSectionsContainer.Children) - section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList()); + section.SelectTypes(mods.NewValue.Select(m => m.GetType()).ToList()); updateMods(); + + updateModSettings(mods); } private void updateMods() @@ -480,6 +460,25 @@ namespace osu.Game.Overlays.Mods UnrankedLabel.FadeTo(ranked ? 0 : 1, 200); } + private void updateModSettings(ValueChangedEvent> selectedMods) + { + foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue)) + { + var controls = added.CreateSettingsControls().ToList(); + if (controls.Count > 0) + ModSettingsContent.Add(new ModControlSection(added) { Children = controls }); + } + + foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue)) + ModSettingsContent.RemoveAll(section => section.Mod == removed); + + bool hasSettings = ModSettingsContent.Children.Count > 0; + CustomiseButton.Enabled.Value = hasSettings; + + if (!hasSettings) + ModSettingsContainer.Hide(); + } + private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) From 5624b9fd3f981451db3c52a7da0c94bc56971a20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 13:19:13 +0900 Subject: [PATCH 37/48] Fix US english --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ec5f99dc31..e8ea43e3f2 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -235,7 +235,7 @@ namespace osu.Game.Overlays.Mods CustomiseButton = new TriangleButton { Width = 180, - Text = "Customization", + Text = "Customisation", Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1, Enabled = { Value = false }, Margin = new MarginPadding From 77b9989e115fa5681e17236415cf6c01f939b863 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 14:10:35 +0900 Subject: [PATCH 38/48] Fix some weird private field names --- .../MathUtils/FastRandom.cs | 20 +-- .../Components/ScrollingTeamContainer.cs | 121 +++++++++--------- osu.Game/Online/Multiplayer/PlaylistItem.cs | 22 ++-- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 34 +++-- 4 files changed, 94 insertions(+), 103 deletions(-) diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs index c721ff862a..46e427e1b7 100644 --- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs +++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs @@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils { private const double int_to_real = 1.0 / (int.MaxValue + 1.0); private const uint int_mask = 0x7FFFFFFF; - private const uint y = 842502087; - private const uint z = 3579807591; - private const uint w = 273326509; - private uint _x, _y = y, _z = z, _w = w; + private const uint y_initial = 842502087; + private const uint z_initial = 3579807591; + private const uint w_initial = 273326509; + private uint x, y = y_initial, z = z_initial, w = w_initial; public FastRandom(int seed) { - _x = (uint)seed; + x = (uint)seed; } public FastRandom() @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils /// The random value. public uint NextUInt() { - uint t = _x ^ (_x << 11); - _x = _y; - _y = _z; - _z = _w; - return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8); + uint t = x ^ (x << 11); + x = y; + y = z; + z = w; + return w = w ^ (w >> 19) ^ t ^ (t >> 8); } /// diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index a345f93896..3ff4718b75 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -83,88 +83,81 @@ namespace osu.Game.Tournament.Screens.Drawings.Components }; } - private ScrollState _scrollState; + private ScrollState scrollState; - private ScrollState scrollState + private void setScrollState(ScrollState newstate) { - get => _scrollState; + if (scrollState == newstate) + return; - set + delayedStateChangeDelegate?.Cancel(); + + switch (scrollState = newstate) { - if (_scrollState == value) - return; + case ScrollState.Scrolling: + resetSelected(); - _scrollState = value; + OnScrollStarted?.Invoke(); - delayedStateChangeDelegate?.Cancel(); + speedTo(1000f, 200); + tracker.FadeOut(100); + break; - switch (value) - { - case ScrollState.Scrolling: - resetSelected(); + case ScrollState.Stopping: + speedTo(0f, 2000); + tracker.FadeIn(200); - OnScrollStarted?.Invoke(); + delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Stopped), 2300); + break; - speedTo(1000f, 200); - tracker.FadeOut(100); + case ScrollState.Stopped: + // Find closest to center + if (!Children.Any()) break; - case ScrollState.Stopping: - speedTo(0f, 2000); - tracker.FadeIn(200); + ScrollingTeam closest = null; - delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300); - break; + foreach (var c in Children) + { + if (!(c is ScrollingTeam stc)) + continue; - case ScrollState.Stopped: - // Find closest to center - if (!Children.Any()) - break; - - ScrollingTeam closest = null; - - foreach (var c in Children) + if (closest == null) { - if (!(c is ScrollingTeam stc)) - continue; - - if (closest == null) - { - closest = stc; - continue; - } - - float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f); - float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f); - - if (o < lastOffset) - closest = stc; + closest = stc; + continue; } - Trace.Assert(closest != null, "closest != null"); + float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f); + float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f); - // ReSharper disable once PossibleNullReferenceException - offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f); + if (o < lastOffset) + closest = stc; + } - ScrollingTeam st = closest; + Trace.Assert(closest != null, "closest != null"); - availableTeams.RemoveAll(at => at == st.Team); + // ReSharper disable once PossibleNullReferenceException + offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f); - st.Selected = true; - OnSelected?.Invoke(st.Team); + ScrollingTeam st = closest; - delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000); - break; + availableTeams.RemoveAll(at => at == st.Team); - case ScrollState.Idle: - resetSelected(); + st.Selected = true; + OnSelected?.Invoke(st.Team); - OnScrollStarted?.Invoke(); + delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Idle), 10000); + break; - speedTo(40f, 200); - tracker.FadeOut(100); - break; - } + case ScrollState.Idle: + resetSelected(); + + OnScrollStarted?.Invoke(); + + speedTo(40f, 200); + tracker.FadeOut(100); + break; } } @@ -176,7 +169,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components availableTeams.Add(team); RemoveAll(c => c is ScrollingTeam); - scrollState = ScrollState.Idle; + setScrollState(ScrollState.Idle); } public void AddTeams(IEnumerable teams) @@ -192,7 +185,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { availableTeams.Clear(); RemoveAll(c => c is ScrollingTeam); - scrollState = ScrollState.Idle; + setScrollState(ScrollState.Idle); } public void RemoveTeam(TournamentTeam team) @@ -217,7 +210,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components if (availableTeams.Count == 0) return; - scrollState = ScrollState.Scrolling; + setScrollState(ScrollState.Scrolling); } public void StopScrolling() @@ -232,13 +225,13 @@ namespace osu.Game.Tournament.Screens.Drawings.Components return; } - scrollState = ScrollState.Stopping; + setScrollState(ScrollState.Stopping); } protected override void LoadComplete() { base.LoadComplete(); - scrollState = ScrollState.Idle; + setScrollState(ScrollState.Idle); } protected override void UpdateAfterChildren() @@ -305,7 +298,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components private void speedTo(float value, double duration = 0, Easing easing = Easing.None) => this.TransformTo(nameof(speed), value, duration, easing); - private enum ScrollState + protected enum ScrollState { None, Idle, diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index e47d497d94..d13e8b31e6 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -45,23 +45,25 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("beatmap")] private APIBeatmap apiBeatmap { get; set; } + private APIMod[] allowedModsBacking; + [JsonProperty("allowed_mods")] private APIMod[] allowedMods { get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); - set => _allowedMods = value; + set => allowedModsBacking = value; } + private APIMod[] requiredModsBacking; + [JsonProperty("required_mods")] private APIMod[] requiredMods { get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); - set => _requiredMods = value; + set => requiredModsBacking = value; } private BeatmapInfo beatmap; - private APIMod[] _allowedMods; - private APIMod[] _requiredMods; public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) { @@ -70,20 +72,20 @@ namespace osu.Game.Online.Multiplayer Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); Ruleset = rulesets.GetRuleset(RulesetID); - if (_allowedMods != null) + if (allowedModsBacking != null) { AllowedMods.Clear(); - AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _allowedMods.Any(m => m.Acronym == mod.Acronym))); + AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => allowedModsBacking.Any(m => m.Acronym == mod.Acronym))); - _allowedMods = null; + allowedModsBacking = null; } - if (_requiredMods != null) + if (requiredModsBacking != null) { RequiredMods.Clear(); - RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _requiredMods.Any(m => m.Acronym == mod.Acronym))); + RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => requiredModsBacking.Any(m => m.Acronym == mod.Acronym))); - _requiredMods = null; + requiredModsBacking = null; } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index f54d638584..2fa5098890 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -188,26 +188,22 @@ namespace osu.Game.Screens.Play InternalButtons.Add(button); } - private int _selectionIndex = -1; + private int selectionIndex = -1; - private int selectionIndex + private void setSelected(int value) { - get => _selectionIndex; - set - { - if (_selectionIndex == value) - return; + if (selectionIndex == value) + return; - // Deselect the previously-selected button - if (_selectionIndex != -1) - InternalButtons[_selectionIndex].Selected.Value = false; + // Deselect the previously-selected button + if (selectionIndex != -1) + InternalButtons[selectionIndex].Selected.Value = false; - _selectionIndex = value; + selectionIndex = value; - // Select the newly-selected button - if (_selectionIndex != -1) - InternalButtons[_selectionIndex].Selected.Value = true; - } + // Select the newly-selected button + if (selectionIndex != -1) + InternalButtons[selectionIndex].Selected.Value = true; } protected override bool OnKeyDown(KeyDownEvent e) @@ -218,16 +214,16 @@ namespace osu.Game.Screens.Play { case Key.Up: if (selectionIndex == -1 || selectionIndex == 0) - selectionIndex = InternalButtons.Count - 1; + setSelected(InternalButtons.Count - 1); else - selectionIndex--; + setSelected(selectionIndex--); return true; case Key.Down: if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) - selectionIndex = 0; + setSelected(0); else - selectionIndex++; + setSelected(selectionIndex++); return true; } } From 9ebad16436cf7a079586c62216964f9f5648f20f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 14:34:17 +0900 Subject: [PATCH 39/48] Fix logic regression --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 2fa5098890..adfbe977a4 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -216,14 +216,14 @@ namespace osu.Game.Screens.Play if (selectionIndex == -1 || selectionIndex == 0) setSelected(InternalButtons.Count - 1); else - setSelected(selectionIndex--); + setSelected(selectionIndex - 1); return true; case Key.Down: if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) setSelected(0); else - setSelected(selectionIndex++); + setSelected(selectionIndex + 1); return true; } } @@ -262,9 +262,9 @@ namespace osu.Game.Screens.Play private void buttonSelectionChanged(DialogButton button, bool isSelected) { if (!isSelected) - selectionIndex = -1; + setSelected(-1); else - selectionIndex = InternalButtons.IndexOf(button); + setSelected(InternalButtons.IndexOf(button)); } private void updateRetryCount() From 6c8f325063affe692a31ba0d3c1cd1688b3456c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 15:24:06 +0900 Subject: [PATCH 40/48] Add failing test --- .../Visual/Gameplay/TestScenePause.cs | 2 - .../Gameplay/TestScenePauseWhenInactive.cs | 49 +++++++++++++++++++ osu.Game/Tests/Visual/TestPlayer.cs | 7 ++- 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 803cab9325..e04315894e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -285,8 +285,6 @@ namespace osu.Game.Tests.Visual.Gameplay protected class PausePlayer : TestPlayer { - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs new file mode 100644 index 0000000000..5f29b46fe6 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. + public class TestScenePauseWhenInactive : PlayerTestScene + { + protected new TestPlayer Player => (TestPlayer)base.Player; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = (Beatmap)base.CreateBeatmap(ruleset); + + beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000); + + return beatmap; + } + + [Resolved] + private GameHost host { get; set; } + + public TestScenePauseWhenInactive() + : base(new OsuRuleset()) + { + } + + [Test] + public void TestDoesntPauseDuringIntro() + { + AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); + AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); + } + + protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); + } +} diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 31f6edadec..8e3821f1a0 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -8,13 +8,16 @@ namespace osu.Game.Tests.Visual { public class TestPlayer : Player { - protected override bool PauseOnFocusLost => false; + protected override bool PauseOnFocusLost { get; } public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - public TestPlayer(bool allowPause = true, bool showResults = true) + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) : base(allowPause, showResults) { + PauseOnFocusLost = pauseOnFocusLost; } } } From 75f92506451b47a1815530d00ffed7a74c23f41b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 15:45:50 +0900 Subject: [PATCH 41/48] Don't automatically pause when window is inactive if in break time --- osu.Game/Screens/Play/Player.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d40c448452..9feee82989 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Play addGameplayComponents(GameplayClockContainer, working); addOverlayComponents(GameplayClockContainer, working); - DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -146,6 +146,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); + breakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState(); } private void addUnderlayComponents(Container target) @@ -241,6 +242,11 @@ namespace osu.Game.Screens.Play }); } + private void updatePauseOnFocusLostState() => + HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost + && !DrawableRuleset.HasReplayLoaded.Value + && !breakOverlay.IsBreakTime.Value; + private WorkingBeatmap loadBeatmap() { WorkingBeatmap working = Beatmap.Value; From 274958669c826236e48f681b8edb3c00ea8bf2b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 15:47:34 +0900 Subject: [PATCH 42/48] Add early assert as sanity check --- osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index 5f29b46fe6..3513b6c25a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -39,7 +39,9 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestDoesntPauseDuringIntro() { AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); } From d8cebd20edac75973ad00c65b35421fcab97a9e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 16:06:20 +0900 Subject: [PATCH 43/48] Add xmldoc --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs index 909c764b9e..573c408a78 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs @@ -8,6 +8,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { public abstract class OsuCursorSprite : CompositeDrawable { + /// + /// The an optional piece of the cursor to expand when in a clicked state. + /// If null, the whole cursor will be affected by expansion. + /// public Drawable ExpandTarget { get; protected set; } } } From 97ca2e2753151ec527264d0226a5e0b2459f6cd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 17:58:59 +0900 Subject: [PATCH 44/48] Add missing bezier option to menu --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index d706fb51d5..ab6064602b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -171,6 +171,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components createMenuItemForPathType(null), createMenuItemForPathType(PathType.Linear), createMenuItemForPathType(PathType.PerfectCurve), + createMenuItemForPathType(PathType.Bezier), createMenuItemForPathType(PathType.Catmull) } } From 03040d175092313cd9a5657fa1f9915917d22927 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 18:18:16 +0900 Subject: [PATCH 45/48] Don't show inherit menu item when first control point is selected --- .../Components/PathControlPointVisualiser.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ab6064602b..e406bb6426 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -27,7 +27,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { internal readonly Container Pieces; + private readonly Slider slider; + private readonly bool allowSelection; private InputManager inputManager; @@ -82,7 +84,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnClick(ClickEvent e) { foreach (var piece in Pieces) + { piece.IsSelected.Value = false; + } + return false; } @@ -156,24 +161,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (!Pieces.Any(p => p.IsHovered)) return null; - int selectedPoints = Pieces.Count(p => p.IsSelected.Value); + var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToList(); + int count = selectedPieces.Count; - if (selectedPoints == 0) + if (count == 0) return null; + List items = new List(); + + if (!selectedPieces.Contains(Pieces[0])) + items.Add(createMenuItemForPathType(null)); + + // todo: hide/disable items which aren't valid for selected points + items.Add(createMenuItemForPathType(PathType.Linear)); + items.Add(createMenuItemForPathType(PathType.PerfectCurve)); + items.Add(createMenuItemForPathType(PathType.Bezier)); + items.Add(createMenuItemForPathType(PathType.Catmull)); + return new MenuItem[] { - new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints, selectedPoints > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()), + new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()), new OsuMenuItem("Type") { - Items = new[] - { - createMenuItemForPathType(null), - createMenuItemForPathType(PathType.Linear), - createMenuItemForPathType(PathType.PerfectCurve), - createMenuItemForPathType(PathType.Bezier), - createMenuItemForPathType(PathType.Catmull) - } + Items = items } }; } From d82ba3e7f7ebb69b29a7529299dbe3aaa800a373 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 18:20:28 +0900 Subject: [PATCH 46/48] Curve -> Curve type --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index e406bb6426..22155ab7af 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return new MenuItem[] { new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()), - new OsuMenuItem("Type") + new OsuMenuItem("Curve type") { Items = items } From 23959f3a3ccb462a21c52460163b70bc474e8fa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 18:52:38 +0900 Subject: [PATCH 47/48] Move control point removal to SliderSelectionBlueprint --- .../Components/PathControlPointVisualiser.cs | 32 ++----------- .../Sliders/SliderSelectionBlueprint.cs | 47 +++++++++++++++++-- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 22155ab7af..cd19653a2e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using Humanizer; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,8 +17,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Compose; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components @@ -34,11 +31,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private InputManager inputManager; - [Resolved(CanBeNull = true)] - private IPlacementHandler placementHandler { get; set; } - private IBindableList controlPoints; + public Action> RemoveControlPointsRequested; + public PathControlPointVisualiser(Slider slider, bool allowSelection) { this.slider = slider; @@ -123,29 +119,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (toRemove.Count == 0) return false; - foreach (var c in toRemove) - { - // The first control point in the slider must have a type, so take it from the previous "first" one - // Todo: Should be handled within SliderPath itself - if (c == slider.Path.ControlPoints[0] && slider.Path.ControlPoints.Count > 1 && slider.Path.ControlPoints[1].Type.Value == null) - slider.Path.ControlPoints[1].Type.Value = slider.Path.ControlPoints[0].Type.Value; - - slider.Path.ControlPoints.Remove(c); - } - - // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted - if (slider.Path.ControlPoints.Count <= 1) - { - placementHandler?.Delete(slider); - return true; - } - - // The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position - // So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0) - Vector2 first = slider.Path.ControlPoints[0].Position.Value; - foreach (var c in slider.Path.ControlPoints) - c.Position.Value -= first; - slider.Position += first; + RemoveControlPointsRequested?.Invoke(toRemove); // Since pieces are re-used, they will not point to the deleted control points while remaining selected foreach (var piece in Pieces) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 68873093a6..3165c441fb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -14,6 +15,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Edit.Compose; using osuTK; using osuTK.Input; @@ -29,6 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } + [Resolved(CanBeNull = true)] + private IPlacementHandler placementHandler { get; set; } + public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { @@ -40,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) + { + RemoveControlPointsRequested = removeControlPoints + } }; } @@ -97,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return true; } + private BindableList controlPoints => HitObject.Path.ControlPoints; + private int addControlPoint(Vector2 position) { position -= HitObject.Position; @@ -104,9 +114,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders int insertionIndex = 0; float minDistance = float.MaxValue; - for (int i = 0; i < HitObject.Path.ControlPoints.Count - 1; i++) + for (int i = 0; i < controlPoints.Count - 1; i++) { - float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position); + float dist = new Line(controlPoints[i].Position.Value, controlPoints[i + 1].Position.Value).DistanceToPoint(position); if (dist < minDistance) { @@ -116,11 +126,42 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Move the control points from the insertion index onwards to make room for the insertion - HitObject.Path.ControlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } }); + controlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } }); return insertionIndex; } + private void removeControlPoints(List toRemove) + { + // Ensure that there are any points to be deleted + if (toRemove.Count == 0) + return; + + foreach (var c in toRemove) + { + // The first control point in the slider must have a type, so take it from the previous "first" one + // Todo: Should be handled within SliderPath itself + if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type.Value == null) + controlPoints[1].Type.Value = controlPoints[0].Type.Value; + + controlPoints.Remove(c); + } + + // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted + if (controlPoints.Count <= 1) + { + placementHandler?.Delete(HitObject); + return; + } + + // The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position + // So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0) + Vector2 first = controlPoints[0].Position.Value; + foreach (var c in controlPoints) + c.Position.Value -= first; + HitObject.Position += first; + } + private void updatePath() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; From 9963d18d17c00777a68d36768429c5ee9ebd4006 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 11 Dec 2019 19:13:04 +0900 Subject: [PATCH 48/48] Add whitespace --- osu.Game/Overlays/RankingsOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index e7c8b94a10..c8874ef891 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -106,6 +106,7 @@ namespace osu.Game.Overlays Scheduler.AddOnce(loadNewContent); }, true); + Scope.BindValueChanged(_ => { // country filtering is only valid for performance scope.