From f53ce5aedfd17d18eaa9a882e14707636a806a92 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 23 Jan 2022 11:11:12 +0800 Subject: [PATCH 0001/1036] Fix max combo calculation in osu diffcalc --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c5b1baaad1..c80b19e1d3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -63,8 +63,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty double drainRate = beatmap.Difficulty.DrainRate; int maxCombo = beatmap.HitObjects.Count; - // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) - maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); + // Add the ticks + tail of the slider + // 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) + // an additional 1 is subtracted if only nested objects are judged because the hit result of the entire slider would not contribute to combo + maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1 - (s.OnlyJudgeNestedObjects ? 1 : 0)); int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); From 44311c1f4e3e18548f9b574de968468d52f8c282 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 23 Jan 2022 11:25:22 +0800 Subject: [PATCH 0002/1036] Add tests for diffcalc max combo --- .../CatchDifficultyCalculatorTest.cs | 12 +++++------ .../ManiaDifficultyCalculatorTest.cs | 12 +++++------ .../OsuDifficultyCalculatorTest.cs | 21 ++++++++++++------- .../TaikoDifficultyCalculatorTest.cs | 16 +++++++------- .../Beatmaps/DifficultyCalculatorTest.cs | 7 +++++-- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 7e8d567fbe..48d46636df 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - [TestCase(4.0505463516206195d, "diffcalc-test")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(4.0505463516206195d, 127, "diffcalc-test")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(5.1696411260785498d, "diffcalc-test")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new CatchModDoubleTime()); + [TestCase(5.1696411260785498d, 127, "diffcalc-test")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new CatchModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 6ec49d7634..715614a201 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.3449735700206298d, "diffcalc-test")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(2.3449735700206298d, 151, "diffcalc-test")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(2.7879104989252959d, "diffcalc-test")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new ManiaModDoubleTime()); + [TestCase(2.7879104989252959d, 151, "diffcalc-test")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index b7984e6995..df577ea8d3 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,15 +15,20 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.6972307565739273d, "diffcalc-test")] - [TestCase(1.4484754139145539d, "zero-length-sliders")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(6.6972307565739273d, 206, "diffcalc-test")] + [TestCase(1.4484754139145539d, 45, "zero-length-sliders")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(8.9382559208689809d, "diffcalc-test")] - [TestCase(1.7548875851757628d, "zero-length-sliders")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new OsuModDoubleTime()); + [TestCase(8.9382559208689809d, 206, "diffcalc-test")] + [TestCase(1.7548875851757628d, 45, "zero-length-sliders")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); + + [TestCase(6.6972307218715166d, 239, "diffcalc-test")] + [TestCase(1.4484754139145537d, 54, "zero-length-sliders")] + public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 2b1cbc580e..226da7df09 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -14,15 +14,15 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(2.2420075288523802d, "diffcalc-test")] - [TestCase(2.2420075288523802d, "diffcalc-test-strong")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(2.2420075288523802d, 200, "diffcalc-test")] + [TestCase(2.2420075288523802d, 200, "diffcalc-test-strong")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(3.134084469440479d, "diffcalc-test")] - [TestCase(3.134084469440479d, "diffcalc-test-strong")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new TaikoModDoubleTime()); + [TestCase(3.134084469440479d, 200, "diffcalc-test")] + [TestCase(3.134084469440479d, 200, "diffcalc-test-strong")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new TaikoModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset().RulesetInfo, beatmap); diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 9f8811c7f9..ed00c7959b 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -22,10 +22,13 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - protected void Test(double expected, string name, params Mod[] mods) + protected void Test(double expectedStarRating, int expectedMaxCombo, string name, params Mod[] mods) { + var attributes = CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods); + // Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences. - Assert.That(CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating, Is.EqualTo(expected).Within(0.00001)); + Assert.That(attributes.StarRating, Is.EqualTo(expectedStarRating).Within(0.00001)); + Assert.That(attributes.MaxCombo, Is.EqualTo(expectedMaxCombo)); } private IWorkingBeatmap getBeatmap(string name) From 74a55ead7711108c4d6b856e11433b476459c35a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 23 Jan 2022 13:00:54 +0800 Subject: [PATCH 0003/1036] Simplify combo counting logic --- .../Difficulty/OsuDifficultyCalculator.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c80b19e1d3..d04d0872d8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; @@ -62,11 +63,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; - int maxCombo = beatmap.HitObjects.Count; - // Add the ticks + tail of the slider - // 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) - // an additional 1 is subtracted if only nested objects are judged because the hit result of the entire slider would not contribute to combo - maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1 - (s.OnlyJudgeNestedObjects ? 1 : 0)); + int maxCombo = 0; + + void countCombo(HitObject ho) + { + if (ho.CreateJudgement().MaxResult.AffectsCombo()) + maxCombo++; + } + + foreach (HitObject ho in beatmap.HitObjects) + { + countCombo(ho); + foreach (HitObject nested in ho.NestedHitObjects) + countCombo(nested); + } int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); From 19ee05c232eb86710001bdad3bef2e135f7bc97a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 17 Jan 2022 20:09:02 +0300 Subject: [PATCH 0004/1036] Add "distance spacing" multiplier for osu! ruleset While osu!catch also implements a distance snap grid, it doesn't rely on `GetBeatSnapDistanceAt` (unlike osu!), therefore it can't have the "distance spacing" multiplier yet. --- .../Edit/CatchHitObjectComposer.cs | 2 + .../Edit/ManiaHitObjectComposer.cs | 2 + .../Edit/OsuHitObjectComposer.cs | 2 + .../Edit/TaikoHitObjectComposer.cs | 2 + .../UserInterface/ExpandableSlider.cs | 9 ++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 101 +++++++++++++++++- 6 files changed, 116 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 164f465438..19595de3b1 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Edit private readonly Bindable distanceSnapToggle = new Bindable(); + protected override bool SupportsDistanceSpacing => false; + private InputManager inputManager; public CatchHitObjectComposer(CatchRuleset ruleset) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 2baec95c94..5752f9f014 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Edit private ManiaBeatSnapGrid beatSnapGrid; private InputManager inputManager; + protected override bool SupportsDistanceSpacing => false; + public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1e84ec80e1..e7eabdc748 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuHitObjectComposer : HitObjectComposer { + protected override bool SupportsDistanceSpacing => true; + public OsuHitObjectComposer(Ruleset ruleset) : base(ruleset) { diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 161799c980..af9bc6c3a3 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { public class TaikoHitObjectComposer : HitObjectComposer { + protected override bool SupportsDistanceSpacing => false; + public TaikoHitObjectComposer(TaikoRuleset ruleset) : base(ruleset) { diff --git a/osu.Game/Graphics/UserInterface/ExpandableSlider.cs b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs index 60e83f9c81..a05c0cfab0 100644 --- a/osu.Game/Graphics/UserInterface/ExpandableSlider.cs +++ b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs @@ -70,6 +70,15 @@ namespace osu.Game.Graphics.UserInterface set => slider.Current = value; } + /// + /// A custom step value for each key press which actuates a change on this control. + /// + public float KeyboardStep + { + get => slider.KeyboardStep; + set => slider.KeyboardStep = value; + } + public BindableBool Expanded { get; } = new BindableBool(); public override bool HandlePositionalInput => true; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 39783cc8bb..528ba2fb8b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,6 +14,8 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings.Sections; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -56,6 +58,26 @@ namespace osu.Game.Rulesets.Edit [Resolved] protected IBeatSnapProvider BeatSnapProvider { get; private set; } + /// + /// Whether this composer supports a "distance spacing" multiplier for distance snap grids. + /// + /// + /// Setting this to displays a "distance spacing" slider and allows this composer to configure the value of . + /// + protected abstract bool SupportsDistanceSpacing { get; } + + private readonly BindableFloat distanceSpacing = new BindableFloat + { + Default = 1.0f, + MinValue = 0.1f, + MaxValue = 6.0f, + Precision = 0.01f, + }; + + public override IBindable DistanceSpacingMultiplier => distanceSpacing; + + private SnappingToolboxContainer snappingToolboxContainer; + protected ComposeBlueprintContainer BlueprintContainer { get; private set; } private DrawableEditorRulesetWrapper drawableRulesetWrapper; @@ -117,6 +139,8 @@ namespace osu.Game.Rulesets.Edit }, new LeftToolboxFlow { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, Children = new Drawable[] { new EditorToolboxGroup("toolbox (1-9)") @@ -132,11 +156,41 @@ namespace osu.Game.Rulesets.Edit Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, - } + }, } }, }; + distanceSpacing.Value = (float)EditorBeatmap.BeatmapInfo.DistanceSpacing; + + if (SupportsDistanceSpacing) + { + ExpandableSlider distanceSpacingSlider; + + AddInternal(snappingToolboxContainer = new SnappingToolboxContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Child = new EditorToolboxGroup("snapping") + { + Child = distanceSpacingSlider = new ExpandableSlider + { + Current = { BindTarget = distanceSpacing }, + KeyboardStep = 0.01f, + } + } + }); + + distanceSpacing.BindValueChanged(v => + { + distanceSpacingSlider.ContractedLabelText = $"D. S. ({v.NewValue:0.##x})"; + distanceSpacingSlider.ExpandedLabelText = $"Distance Spacing ({v.NewValue:0.##x})"; + EditorBeatmap.BeatmapInfo.DistanceSpacing = v.NewValue; + }, true); + } + else + distanceSpacing.Disabled = true; + toolboxCollection.Items = CompositionTools .Prepend(new SelectTool()) .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) @@ -211,8 +265,17 @@ namespace osu.Game.Rulesets.Edit #region Tool selection logic + private bool distanceSpacingScrollActive; + protected override bool OnKeyDown(KeyDownEvent e) { + if (SupportsDistanceSpacing && e.AltPressed && e.Key == Key.D && !e.Repeat) + { + snappingToolboxContainer.Expanded.Value = true; + distanceSpacingScrollActive = true; + return true; + } + if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; @@ -242,6 +305,28 @@ namespace osu.Game.Rulesets.Edit return base.OnKeyDown(e); } + protected override void OnKeyUp(KeyUpEvent e) + { + if (distanceSpacingScrollActive && (e.Key == Key.AltLeft || e.Key == Key.AltRight || e.Key == Key.D)) + { + snappingToolboxContainer.Expanded.Value = false; + distanceSpacingScrollActive = false; + } + + base.OnKeyUp(e); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (distanceSpacingScrollActive) + { + distanceSpacing.Value += e.ScrollDelta.Y * (e.ShiftPressed || e.IsPrecise ? 0.01f : 0.1f); + return true; + } + + return base.OnScroll(e); + } + private bool checkLeftToggleFromKey(Key key, out int index) { if (key < Key.Number1 || key > Key.Number9) @@ -383,7 +468,7 @@ namespace osu.Game.Rulesets.Edit public override float GetBeatSnapDistanceAt(HitObject referenceObject) { - return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor); + return (float)(100 * referenceObject.DifficultyControlPoint.SliderVelocity * EditorBeatmap.Difficulty.SliderMultiplier * distanceSpacing.Value / BeatSnapProvider.BeatDivisor); } public override float DurationToDistance(HitObject referenceObject, double duration) @@ -432,6 +517,18 @@ namespace osu.Game.Rulesets.Edit FillFlow.Spacing = new Vector2(10); } } + + private class SnappingToolboxContainer : ExpandingContainer + { + public SnappingToolboxContainer() + : base(130, 250) + { + RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding { Left = 10 }; + + FillFlow.Spacing = new Vector2(10); + } + } } /// From 528dc03b8c06b94d1b45c0d07e8b681ba109fe00 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 18 Jan 2022 15:25:32 +0300 Subject: [PATCH 0005/1036] Expose distance spacing in `IPositionSnapProvider` for updating distance grid Alternate method is to expose a `SnapDistancesChanged` event in `IPositionSnapProvider` instead, but I chose this way as an analogue to `IBeatSnapProvider.BeatDivisor`, which might even make sense to be exposed as `BindableBeatDivisor` instead of caching that separately. --- .../Editor/TestSceneManiaBeatSnapGrid.cs | 3 +++ .../Editor/TestSceneOsuDistanceSnapGrid.cs | 3 +++ .../Visual/Editing/TestSceneDistanceSnapGrid.cs | 3 +++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 ++ osu.Game/Rulesets/Edit/IPositionSnapProvider.cs | 8 ++++++++ .../Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 6 ++++++ .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 ++ 7 files changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 50be13c4e0..38779ab47b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Timing; @@ -98,6 +99,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor set => InternalChild = value; } + public override IBindable DistanceSpacingMultiplier => throw new System.NotImplementedException(); + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { throw new System.NotImplementedException(); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index c770e2d96f..c5829d5351 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -186,6 +187,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); + public IBindable DistanceSpacingMultiplier { get; } = new BindableFloat(1); + public float GetBeatSnapDistanceAt(HitObject referenceObject) => (float)beat_length; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 0d9e06e471..cba6d7692d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; @@ -166,6 +167,8 @@ namespace osu.Game.Tests.Visual.Editing public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); + public IBindable DistanceSpacingMultiplier { get; } = new BindableFloat(1); + public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 528ba2fb8b..6cdc2699aa 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -563,6 +563,8 @@ namespace osu.Game.Rulesets.Edit #region IPositionSnapProvider + public abstract IBindable DistanceSpacingMultiplier { get; } + public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index 743a2f41fc..1cfcb86e10 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -1,6 +1,8 @@ // 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.Bindables; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osuTK; @@ -8,6 +10,12 @@ namespace osu.Game.Rulesets.Edit { public interface IPositionSnapProvider { + /// + /// The spacing multiplier applied to beat snap distances. + /// + /// + IBindable DistanceSpacingMultiplier { get; } + /// /// Given a position, find a valid time and position snap. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 05bf405f3c..afbe83ec83 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -52,6 +53,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + private IBindable distanceSpacingMultiplier; + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly double? endTime; @@ -82,6 +85,9 @@ namespace osu.Game.Screens.Edit.Compose.Components base.LoadComplete(); beatDivisor.BindValueChanged(_ => updateSpacing(), true); + + distanceSpacingMultiplier = SnapProvider.DistanceSpacingMultiplier.GetBoundCopy(); + distanceSpacingMultiplier.BindValueChanged(_ => updateSpacing(), true); } private void updateSpacing() diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 51cca4ceff..f3a328c6bc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -317,6 +317,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; + public IBindable DistanceSpacingMultiplier => throw new NotImplementedException(); + public float GetBeatSnapDistanceAt(HitObject referenceObject) => throw new NotImplementedException(); public float DurationToDistance(HitObject referenceObject, double duration) => throw new NotImplementedException(); From 9504942cfda38af4e6c5c15450a8258f1082de8c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 18 Jan 2022 18:30:31 +0300 Subject: [PATCH 0006/1036] Add test case for distance spacing hotkeys scrolling behaviour --- .../Editing/TestSceneHitObjectComposer.cs | 24 +++++++++++++++++++ osu.Game/Overlays/SettingsToolboxGroup.cs | 7 ++++++ 2 files changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 145d738f60..7ac511e761 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; @@ -21,6 +22,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -86,6 +88,28 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); } + [Test] + public void TestDistanceSpacingHotkeys() + { + float originalSpacing = 0; + + AddStep("retrieve original spacing", () => originalSpacing = (float)editorBeatmap.BeatmapInfo.DistanceSpacing); + + AddStep("hold alt", () => InputManager.PressKey(Key.LAlt)); + AddStep("hold D", () => InputManager.PressKey(Key.D)); + + AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); + AddAssert("distance spacing increased by 0.5", () => Precision.AlmostEquals(editorBeatmap.BeatmapInfo.DistanceSpacing, originalSpacing + 0.5f)); + + AddStep("hold shift", () => InputManager.PressKey(Key.LShift)); + AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); + AddAssert("distance spacing increased by 0.05", () => Precision.AlmostEquals(editorBeatmap.BeatmapInfo.DistanceSpacing, originalSpacing + 0.55f)); + + AddStep("release shift", () => InputManager.ReleaseKey(Key.LShift)); + AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt)); + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + } + public class EditorBeatmapContainer : Container { private readonly IWorkingBeatmap working; diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 08321f68fe..509410ec20 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -22,6 +22,11 @@ namespace osu.Game.Overlays { public class SettingsToolboxGroup : Container, IExpandable { + /// + /// The title of this toolbox group. + /// + public string Title { get; } + private const float transition_duration = 250; private const int container_width = 270; private const int border_thickness = 2; @@ -48,6 +53,8 @@ namespace osu.Game.Overlays /// The title to be displayed in the header of this group. public SettingsToolboxGroup(string title) { + Title = title; + AutoSizeAxes = Axes.Y; Width = container_width; Masking = true; From 500322ff214753e17a78ca5bb601412ba27e7447 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jan 2022 06:13:56 +0300 Subject: [PATCH 0007/1036] Update failing snap test scenes --- .../Editor/TestSceneObjectObjectSnap.cs | 7 +++++++ .../Editor/TestSceneSliderSnapping.cs | 10 +++++++++- .../TestSceneHitObjectComposerDistanceSnapping.cs | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 7bdf131e0d..41304dcd24 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -3,9 +3,11 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Tests.Beatmaps; @@ -26,6 +28,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor base.SetUpSteps(); AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); + AddStep("set distance spacing to 1", () => + { + var distanceSpacing = (BindableFloat)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + distanceSpacing.Value = 1; + }); } [TestCase(true)] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index b43b2b1461..23131668a0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -3,12 +3,15 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; @@ -61,9 +64,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor })); AddStep("set beat divisor to 1/1", () => { - var beatDivisor = (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor)); + var beatDivisor = Editor.Dependencies.Get(); beatDivisor.Value = 1; }); + AddStep("set distance spacing to 1", () => + { + var distanceSpacing = (BindableFloat)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + distanceSpacing.Value = 1; + }); } [Test] diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 43f22e4e90..d9312cb7ae 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -61,6 +61,7 @@ namespace osu.Game.Tests.Editing BeatDivisor.Value = 1; composer.EditorBeatmap.Difficulty.SliderMultiplier = 1; + composer.EditorBeatmap.BeatmapInfo.DistanceSpacing = 1; composer.EditorBeatmap.ControlPointInfo.Clear(); composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); }); From 868dcd20f56d208d41bdc769f4266b91fd355c84 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 11:39:31 +0300 Subject: [PATCH 0008/1036] Remove `e.ShiftPressed` handling for now Broken on macOS, will be handled differently later on as discussed. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 6cdc2699aa..948e1d770f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -320,7 +320,7 @@ namespace osu.Game.Rulesets.Edit { if (distanceSpacingScrollActive) { - distanceSpacing.Value += e.ScrollDelta.Y * (e.ShiftPressed || e.IsPrecise ? 0.01f : 0.1f); + distanceSpacing.Value += e.ScrollDelta.Y * (e.IsPrecise ? 0.01f : 0.1f); return true; } From cbc8f7ff90e95b0d0b1876ac8f0634ac5575f643 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 15 Feb 2022 02:29:09 +0300 Subject: [PATCH 0009/1036] Remove test coverage of "shift" hotkey behaviour --- osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 7ac511e761..d542732e19 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -101,11 +101,6 @@ namespace osu.Game.Tests.Visual.Editing AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); AddAssert("distance spacing increased by 0.5", () => Precision.AlmostEquals(editorBeatmap.BeatmapInfo.DistanceSpacing, originalSpacing + 0.5f)); - AddStep("hold shift", () => InputManager.PressKey(Key.LShift)); - AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); - AddAssert("distance spacing increased by 0.05", () => Precision.AlmostEquals(editorBeatmap.BeatmapInfo.DistanceSpacing, originalSpacing + 0.55f)); - - AddStep("release shift", () => InputManager.ReleaseKey(Key.LShift)); AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt)); AddStep("release D", () => InputManager.ReleaseKey(Key.D)); } From f7edf25d4c84e2bc61a3f95efd22de52409fc66b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 03:23:02 +0300 Subject: [PATCH 0010/1036] Remove no longer required property --- osu.Game/Overlays/SettingsToolboxGroup.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 509410ec20..08321f68fe 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -22,11 +22,6 @@ namespace osu.Game.Overlays { public class SettingsToolboxGroup : Container, IExpandable { - /// - /// The title of this toolbox group. - /// - public string Title { get; } - private const float transition_duration = 250; private const int container_width = 270; private const int border_thickness = 2; @@ -53,8 +48,6 @@ namespace osu.Game.Overlays /// The title to be displayed in the header of this group. public SettingsToolboxGroup(string title) { - Title = title; - AutoSizeAxes = Axes.Y; Width = container_width; Masking = true; From 7654584e40e8860a20f6bfce1322d6b331da92af Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 03:25:03 +0300 Subject: [PATCH 0011/1036] Allow using `SizeSlider` on different value types --- .../Visual/UserInterface/TestSceneExpandingContainer.cs | 4 ++-- .../Overlays/Settings/Sections/Gameplay/InputSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 6 ++++-- .../Settings/Sections/UserInterface/GeneralSettings.cs | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs index f4920b4412..288c0cb140 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface private TestExpandingContainer container; private SettingsToolboxGroup toolboxGroup; - private ExpandableSlider slider1; + private ExpandableSlider> slider1; private ExpandableSlider slider2; [SetUp] @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.UserInterface Width = 1, Children = new Drawable[] { - slider1 = new ExpandableSlider + slider1 = new ExpandableSlider> { Current = new BindableFloat { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs index 962572ca6e..83ea655601 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { Children = new Drawable[] { - new SettingsSlider + new SettingsSlider> { LabelText = SkinSettingsStrings.GameplayCursorSize, Current = config.GetBindable(OsuSetting.GameplayCursorSize), diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 8aeb440be1..cc2c77adf2 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.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 osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; @@ -9,8 +10,9 @@ namespace osu.Game.Overlays.Settings.Sections /// /// A slider intended to show a "size" multiplier number, where 1x is 1.0. /// - internal class SizeSlider : OsuSliderBar + internal class SizeSlider : OsuSliderBar + where T : struct, IEquatable, IComparable, IConvertible, IFormattable { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", null); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 0afbed5df5..66702744d3 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.CursorRotation, Current = config.GetBindable(OsuSetting.CursorRotation) }, - new SettingsSlider + new SettingsSlider> { LabelText = UserInterfaceStrings.MenuCursorSize, Current = config.GetBindable(OsuSetting.MenuCursorSize), From 0992bec2c849d9e5a7be97d1d9396027a1f8f4e1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 03:28:05 +0300 Subject: [PATCH 0012/1036] Change "distance spacing" multipler type to `double` Avoids losing precision on initial load, causing an unnecessary hash change in `EditorChangeHandler`. Resolves test failures in `TestSceneEditorChangeStates` (https://github.com/ppy/osu/runs/5192493482?check_suite_focus=true). --- .../Editor/TestSceneManiaBeatSnapGrid.cs | 2 +- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 2 +- .../Editing/TestSceneDistanceSnapGrid.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 20 +++++++++---------- .../Rulesets/Edit/IPositionSnapProvider.cs | 2 +- .../Compose/Components/DistanceSnapGrid.cs | 2 +- .../Compose/Components/Timeline/Timeline.cs | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 38779ab47b..f32a1a823e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor set => InternalChild = value; } - public override IBindable DistanceSpacingMultiplier => throw new System.NotImplementedException(); + public override IBindable DistanceSpacingMultiplier => throw new System.NotImplementedException(); public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index c5829d5351..38a442d48b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); - public IBindable DistanceSpacingMultiplier { get; } = new BindableFloat(1); + public IBindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); public float GetBeatSnapDistanceAt(HitObject referenceObject) => (float)beat_length; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index cba6d7692d..69fd642143 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Editing public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); - public IBindable DistanceSpacingMultiplier { get; } = new BindableFloat(1); + public IBindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 948e1d770f..ab82fbad04 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -66,15 +66,15 @@ namespace osu.Game.Rulesets.Edit /// protected abstract bool SupportsDistanceSpacing { get; } - private readonly BindableFloat distanceSpacing = new BindableFloat + private readonly BindableDouble distanceSpacing = new BindableDouble { - Default = 1.0f, - MinValue = 0.1f, - MaxValue = 6.0f, - Precision = 0.01f, + Default = 1.0, + MinValue = 0.1, + MaxValue = 6.0, + Precision = 0.01, }; - public override IBindable DistanceSpacingMultiplier => distanceSpacing; + public override IBindable DistanceSpacingMultiplier => distanceSpacing; private SnappingToolboxContainer snappingToolboxContainer; @@ -161,11 +161,11 @@ namespace osu.Game.Rulesets.Edit }, }; - distanceSpacing.Value = (float)EditorBeatmap.BeatmapInfo.DistanceSpacing; + distanceSpacing.Value = EditorBeatmap.BeatmapInfo.DistanceSpacing; if (SupportsDistanceSpacing) { - ExpandableSlider distanceSpacingSlider; + ExpandableSlider> distanceSpacingSlider; AddInternal(snappingToolboxContainer = new SnappingToolboxContainer { @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Edit Origin = Anchor.TopRight, Child = new EditorToolboxGroup("snapping") { - Child = distanceSpacingSlider = new ExpandableSlider + Child = distanceSpacingSlider = new ExpandableSlider> { Current = { BindTarget = distanceSpacing }, KeyboardStep = 0.01f, @@ -563,7 +563,7 @@ namespace osu.Game.Rulesets.Edit #region IPositionSnapProvider - public abstract IBindable DistanceSpacingMultiplier { get; } + public abstract IBindable DistanceSpacingMultiplier { get; } public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index 1cfcb86e10..e4adbba47d 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// The spacing multiplier applied to beat snap distances. /// /// - IBindable DistanceSpacingMultiplier { get; } + IBindable DistanceSpacingMultiplier { get; } /// /// Given a position, find a valid time and position snap. diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index afbe83ec83..f9e4ef086c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - private IBindable distanceSpacingMultiplier; + private IBindable distanceSpacingMultiplier; private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly double? endTime; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index f3a328c6bc..8832c4e89c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -317,7 +317,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; - public IBindable DistanceSpacingMultiplier => throw new NotImplementedException(); + public IBindable DistanceSpacingMultiplier => throw new NotImplementedException(); public float GetBeatSnapDistanceAt(HitObject referenceObject) => throw new NotImplementedException(); From 215da7e933ded0423618e6fb6f58e28c65ea2339 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 12:05:55 +0900 Subject: [PATCH 0013/1036] Reimplement as extension method on IBeatmap Implementation has changed slightly to support arbitrary levels of nested hitobjects. --- .../Difficulty/OsuDifficultyCalculator.cs | 17 +------------ osu.Game/Beatmaps/IBeatmap.cs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index d04d0872d8..df6fd19d36 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; @@ -62,21 +61,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; - - int maxCombo = 0; - - void countCombo(HitObject ho) - { - if (ho.CreateJudgement().MaxResult.AffectsCombo()) - maxCombo++; - } - - foreach (HitObject ho in beatmap.HitObjects) - { - countCombo(ho); - foreach (HitObject nested in ho.NestedHitObjects) - countCombo(nested); - } + int maxCombo = beatmap.GetMaxCombo(); int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3f598cd1e5..dec1ef4294 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Beatmaps { @@ -70,4 +71,27 @@ namespace osu.Game.Beatmaps /// new IReadOnlyList HitObjects { get; } } + + public static class BeatmapExtensions + { + /// + /// Finds the maximum achievable combo by hitting all s in a beatmap. + /// + public static int GetMaxCombo(this IBeatmap beatmap) + { + int combo = 0; + foreach (var h in beatmap.HitObjects) + addCombo(h, ref combo); + return combo; + + static void addCombo(HitObject hitObject, ref int combo) + { + if (hitObject.CreateJudgement().MaxResult.AffectsCombo()) + combo++; + + foreach (var nested in hitObject.NestedHitObjects) + addCombo(nested, ref combo); + } + } + } } From 8b30c847f9ab3829b8fc8bc0e5f944a217cba8e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 08:39:08 +0300 Subject: [PATCH 0014/1036] Update outdated bindable casts in tests --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs | 2 +- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 41304dcd24..aa0e35102e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); AddStep("set distance spacing to 1", () => { - var distanceSpacing = (BindableFloat)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + var distanceSpacing = (BindableDouble)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; distanceSpacing.Value = 1; }); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index 23131668a0..13ce340362 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); AddStep("set distance spacing to 1", () => { - var distanceSpacing = (BindableFloat)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + var distanceSpacing = (BindableDouble)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; distanceSpacing.Value = 1; }); } From b66566e96d4f22d7e4351443ea8d999fd7f9bce7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 10:35:57 +0300 Subject: [PATCH 0015/1036] Use explicit culture info rather than `null` --- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index cc2c77adf2..26db1aa755 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; @@ -13,6 +14,6 @@ namespace osu.Game.Overlays.Settings.Sections internal class SizeSlider : OsuSliderBar where T : struct, IEquatable, IComparable, IConvertible, IFormattable { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", null); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", NumberFormatInfo.CurrentInfo); } } From 3945cd24ebb84579fe1492c083d820155581f652 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Feb 2022 21:14:49 +0900 Subject: [PATCH 0016/1036] wip --- .../Rulesets/Difficulty/DifficultyCalculator.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 6b61dd3efb..7d6c235fc1 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Utils; namespace osu.Game.Rulesets.Difficulty { @@ -122,12 +123,17 @@ namespace osu.Game.Rulesets.Difficulty /// A collection of structures describing the difficulty of the beatmap for each mod combination. public IEnumerable CalculateAll(CancellationToken cancellationToken = default) { + var rulesetInstance = ruleset.CreateInstance(); + foreach (var combination in CreateDifficultyAdjustmentModCombinations()) { - if (combination is MultiMod multi) - yield return Calculate(multi.Mods, cancellationToken); - else - yield return Calculate(combination.Yield(), cancellationToken); + Mod classicMod = rulesetInstance.CreateAllMods().SingleOrDefault(m => m is ModClassic); + + var finalCombination = ModUtils.FlattenMod(combination); + if (classicMod != null) + finalCombination = finalCombination.Append(classicMod); + + yield return Calculate(finalCombination.ToArray(), cancellationToken); } } From bedd07d2e4dca93e160949614f9f1a75f03a2fe2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 22 Feb 2022 18:12:55 +0900 Subject: [PATCH 0017/1036] Add remark about usage of CalculateAll() --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 7d6c235fc1..0935f26de6 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -120,6 +120,9 @@ namespace osu.Game.Rulesets.Difficulty /// /// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap. /// + /// + /// This should only be used to compute difficulties for legacy mod combinations. + /// /// A collection of structures describing the difficulty of the beatmap for each mod combination. public IEnumerable CalculateAll(CancellationToken cancellationToken = default) { From 76e64f501357db6a95a472c34a5389199c459aae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 14:22:39 +0300 Subject: [PATCH 0018/1036] Use manual framed clock for lead-in player test scene --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index b195d2aa74..5a1fc1b1e5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; @@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create player", () => { - Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard); + Beatmap.Value = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), Audio); LoadScreen(player = new LeadInPlayer()); }); From e2001148d570ca139df57cf7cbd606adf8f50363 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 8 Mar 2022 21:47:54 +0000 Subject: [PATCH 0019/1036] Implement strict tracking mod --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 3 + .../Mods/OsuModStrictTracking.cs | 59 +++++++++++++++++++ .../Objects/SliderTailCircle.cs | 4 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index e04a30d06c..f46573c494 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.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.Bindables; using osu.Game.Configuration; @@ -16,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset { + public override Type[] IncompatibleMods => new[] { typeof(OsuModStrictTracking) }; + [SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")] public Bindable NoSliderHeadAccuracy { get; } = new BindableBool(true); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs new file mode 100644 index 0000000000..13da422049 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.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 osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModStrictTracking : Mod, IApplicableToDifficulty, IApplicableToDrawableHitObject, IApplicableToHitObject + { + public override string Name => @"Strict Tracking"; + public override string Acronym => @"ST"; + public override IconUsage? Icon => FontAwesome.Solid.PenFancy; + public override ModType Type => ModType.DifficultyIncrease; + public override string Description => @"Follow circles just got serious..."; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModClassic) }; + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + difficulty.SliderTickRate = 0.0; + } + + public void ApplyToDrawableHitObject(DrawableHitObject drawable) + { + if (drawable is DrawableSlider slider) + { + slider.Tracking.ValueChanged += e => + { + if (e.NewValue || slider.Judged) return; + + slider.MissForcefully(); + + foreach (var o in slider.NestedHitObjects) + { + if (o is DrawableOsuHitObject h && !o.Judged) + h.MissForcefully(); + } + }; + } + } + + public void ApplyToHitObject(HitObject hitObject) + { + switch (hitObject) + { + case Slider slider: + slider.TailCircle.JudgeAsSliderTick = true; + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index f9450062f4..70459dc432 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -14,12 +14,14 @@ namespace osu.Game.Rulesets.Osu.Objects /// public class SliderTailCircle : SliderEndCircle { + public bool JudgeAsSliderTick = false; + public SliderTailCircle(Slider slider) : base(slider) { } - public override Judgement CreateJudgement() => new SliderTailJudgement(); + public override Judgement CreateJudgement() => JudgeAsSliderTick ? (OsuJudgement)new SliderTickJudgement() : new SliderTailJudgement(); public class SliderTailJudgement : OsuJudgement { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5ade164566..faaab1a8e2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,6 +159,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), + new OsuModStrictTracking() }; case ModType.Conversion: From 37328f8d245fc9d50e3a14bfe57a3d9c73d0f1d1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 20:36:31 +0800 Subject: [PATCH 0020/1036] Extract hit object positioning logic to a separate class It is intentional to not rename the identifiers at this point to produce a cleaner diff. --- .../Mods/OsuHitObjectPositionModifier.cs | 346 ++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 325 +--------------- 2 files changed, 354 insertions(+), 317 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs new file mode 100644 index 0000000000..3242b99755 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs @@ -0,0 +1,346 @@ +// 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 osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; +using osuTK; + +#nullable enable + +namespace osu.Game.Rulesets.Osu.Mods +{ + /// + /// Places hit objects according to information in while keeping objects inside the playfield. + /// + public class OsuHitObjectPositionModifier + { + /// + /// Number of previous hitobjects to be shifted together when an object is being moved. + /// + private const int preceding_hitobjects_to_shift = 10; + + private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; + + private readonly List hitObjects; + + private readonly List randomObjects = new List(); + + /// + /// Contains information specifying how each hit object should be placed. + /// The default values correspond to how objects are originally placed in the beatmap. + /// + public IReadOnlyList RandomObjects => randomObjects; + + public OsuHitObjectPositionModifier(List hitObjects) + { + this.hitObjects = hitObjects; + populateHitObjectPositions(); + } + + private void populateHitObjectPositions() + { + Vector2 previousPosition = playfield_centre; + float previousAngle = 0; + + foreach (OsuHitObject hitObject in hitObjects) + { + Vector2 relativePosition = hitObject.Position - previousPosition; + float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + float relativeAngle = absoluteAngle - previousAngle; + + randomObjects.Add(new RandomObjectInfo(hitObject) + { + RelativeAngle = relativeAngle, + DistanceFromPrevious = relativePosition.Length + }); + + previousPosition = hitObject.EndPosition; + previousAngle = absoluteAngle; + } + } + + /// + /// Reposition the hit objects according to the information in . + /// + public void ApplyRandomisation() + { + RandomObjectInfo? previous = null; + + for (int i = 0; i < hitObjects.Count; i++) + { + var hitObject = hitObjects[i]; + + var current = randomObjects[i]; + + if (hitObject is Spinner) + { + previous = null; + continue; + } + + computeRandomisedPosition(current, previous, i > 1 ? randomObjects[i - 2] : null); + + // Move hit objects back into the playfield if they are outside of it + Vector2 shift = Vector2.Zero; + + switch (hitObject) + { + case HitCircle circle: + shift = clampHitCircleToPlayfield(circle, current); + break; + + case Slider slider: + shift = clampSliderToPlayfield(slider, current); + break; + } + + if (shift != Vector2.Zero) + { + var toBeShifted = new List(); + + for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) + { + // only shift hit circles + if (!(hitObjects[j] is HitCircle)) break; + + toBeShifted.Add(hitObjects[j]); + } + + if (toBeShifted.Count > 0) + applyDecreasingShift(toBeShifted, shift); + } + + previous = current; + } + } + + /// + /// Compute the randomised position of a hit object while attempting to keep it inside the playfield. + /// + /// The representing the hit object to have the randomised position computed for. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private void computeRandomisedPosition(RandomObjectInfo current, RandomObjectInfo? previous, RandomObjectInfo? beforePrevious) + { + float previousAbsoluteAngle = 0f; + + if (previous != null) + { + Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre; + Vector2 relativePosition = previous.HitObject.Position - earliestPosition; + previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + } + + float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; + + var posRelativeToPrev = new Vector2( + current.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), + current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) + ); + + Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre; + + posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); + + current.PositionRandomised = lastEndPosition + posRelativeToPrev; + } + + /// + /// Move the randomised position of a hit circle so that it fits inside the playfield. + /// + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) + { + var previousPosition = objectInfo.PositionRandomised; + objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( + objectInfo.PositionRandomised, + (float)circle.Radius + ); + + circle.Position = objectInfo.PositionRandomised; + + return objectInfo.PositionRandomised - previousPosition; + } + + /// + /// Moves the and all necessary nested s into the if they aren't already. + /// + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) + { + var possibleMovementBounds = calculatePossibleMovementBounds(slider); + + var previousPosition = objectInfo.PositionRandomised; + + // Clamp slider position to the placement area + // If the slider is larger than the playfield, force it to stay at the original position + float newX = possibleMovementBounds.Width < 0 + ? objectInfo.PositionOriginal.X + : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); + + float newY = possibleMovementBounds.Height < 0 + ? objectInfo.PositionOriginal.Y + : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); + + slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); + objectInfo.EndPositionRandomised = slider.EndPosition; + + shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal); + + return objectInfo.PositionRandomised - previousPosition; + } + + /// + /// Decreasingly shift a list of s by a specified amount. + /// The first item in the list is shifted by the largest amount, while the last item is shifted by the smallest amount. + /// + /// The list of hit objects to be shifted. + /// The amount to be shifted. + private void applyDecreasingShift(IList hitObjects, Vector2 shift) + { + for (int i = 0; i < hitObjects.Count; i++) + { + var hitObject = hitObjects[i]; + // The first object is shifted by a vector slightly smaller than shift + // The last object is shifted by a vector slightly larger than zero + Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); + + hitObject.Position = clampToPlayfieldWithPadding(position, (float)hitObject.Radius); + } + } + + /// + /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) + /// such that the entire slider is inside the playfield. + /// + /// + /// If the slider is larger than the playfield, the returned may have negative width/height. + /// + private RectangleF calculatePossibleMovementBounds(Slider slider) + { + var pathPositions = new List(); + slider.Path.GetPathToProgress(pathPositions, 0, 1); + + float minX = float.PositiveInfinity; + float maxX = float.NegativeInfinity; + + float minY = float.PositiveInfinity; + float maxY = float.NegativeInfinity; + + // Compute the bounding box of the slider. + foreach (var pos in pathPositions) + { + minX = MathF.Min(minX, pos.X); + maxX = MathF.Max(maxX, pos.X); + + minY = MathF.Min(minY, pos.Y); + maxY = MathF.Max(maxY, pos.Y); + } + + // Take the circle radius into account. + float radius = (float)slider.Radius; + + minX -= radius; + minY -= radius; + + maxX += radius; + maxY += radius; + + // Given the bounding box of the slider (via min/max X/Y), + // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), + // and the amount that it can move to the right is WIDTH - maxX. + // Same calculation applies for the Y axis. + float left = -minX; + float right = OsuPlayfield.BASE_SIZE.X - maxX; + float top = -minY; + float bottom = OsuPlayfield.BASE_SIZE.Y - maxY; + + return new RectangleF(left, top, right - left, bottom - top); + } + + /// + /// Shifts all nested s and s by the specified shift. + /// + /// whose nested s and s should be shifted + /// The the 's nested s and s should be shifted by + private void shiftNestedObjects(Slider slider, Vector2 shift) + { + foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) + { + if (!(hitObject is OsuHitObject osuHitObject)) + continue; + + osuHitObject.Position += shift; + } + } + + /// + /// Clamp a position to playfield, keeping a specified distance from the edges. + /// + /// The position to be clamped. + /// The minimum distance allowed from playfield edges. + /// The clamped position. + private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) + { + return new Vector2( + Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), + Math.Clamp(position.Y, padding, OsuPlayfield.BASE_SIZE.Y - padding) + ); + } + + public interface IRandomObjectInfo + { + /// + /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. + /// + /// + /// of the first hit object in a beatmap represents the absolute angle from playfield center to the object. + /// + /// + /// If is 0, the player's cursor doesn't need to change its direction of movement when passing + /// the previous object to reach this one. + /// + float RelativeAngle { get; set; } + + /// + /// The jump distance from the previous hit object to this one. + /// + /// + /// of the first hit object in a beatmap is relative to the playfield center. + /// + float DistanceFromPrevious { get; set; } + + /// + /// The hit object associated with this . + /// + OsuHitObject HitObject { get; } + } + + private class RandomObjectInfo : IRandomObjectInfo + { + public float RelativeAngle { get; set; } + + public float DistanceFromPrevious { get; set; } + + public Vector2 PositionOriginal { get; } + public Vector2 PositionRandomised { get; set; } + + public Vector2 EndPositionOriginal { get; } + public Vector2 EndPositionRandomised { get; set; } + + public OsuHitObject HitObject { get; } + + public RandomObjectInfo(OsuHitObject hitObject) + { + PositionRandomised = PositionOriginal = hitObject.Position; + EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; + HitObject = hitObject; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 7479c3120a..2c38be6c16 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -4,19 +4,13 @@ #nullable enable using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Osu.Utils; -using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -28,12 +22,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "It never gets boring!"; private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; - private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; - - /// - /// Number of previous hitobjects to be shifted together when another object is being moved. - /// - private const int preceding_hitobjects_to_shift = 10; private Random? rng; @@ -42,330 +30,33 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(beatmap is OsuBeatmap osuBeatmap)) return; - var hitObjects = osuBeatmap.HitObjects; - Seed.Value ??= RNG.Next(); rng = new Random((int)Seed.Value); - var randomObjects = randomiseObjects(hitObjects); + var positionModifier = new OsuHitObjectPositionModifier(osuBeatmap.HitObjects); - applyRandomisation(hitObjects, randomObjects); - } - - /// - /// Randomise the position of each hit object and return a list of s describing how each hit object should be placed. - /// - /// A list of s to have their positions randomised. - /// A list of s describing how each hit object should be placed. - private List randomiseObjects(IEnumerable hitObjects) - { - Debug.Assert(rng != null, $"{nameof(ApplyToBeatmap)} was not called before randomising objects"); - - var randomObjects = new List(); - RandomObjectInfo? previous = null; float rateOfChangeMultiplier = 0; - foreach (OsuHitObject hitObject in hitObjects) + foreach (var positionInfo in positionModifier.RandomObjects) { - var current = new RandomObjectInfo(hitObject); - randomObjects.Add(current); - // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams - if (hitObject.IndexInCurrentCombo % 5 == 0) + if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - if (previous == null) + if (positionInfo == positionModifier.RandomObjects.First()) { - current.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2); - current.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2; + positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); } else { - current.DistanceFromPrevious = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal); - - // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) - // is proportional to the distance between the last and the current hit object - // to allow jumps and prevent too sharp turns during streams. - - // Allow maximum jump angle when jump distance is more than half of playfield diagonal length - current.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, current.DistanceFromPrevious / (playfield_diagonal * 0.5f)); + positionInfo.RelativeAngle = (float)(rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, positionInfo.DistanceFromPrevious / (playfield_diagonal * 0.5f))); } - - previous = current; } - return randomObjects; - } - - /// - /// Reposition the hit objects according to the information in . - /// - /// The hit objects to be repositioned. - /// A list of describing how each hit object should be placed. - private void applyRandomisation(IReadOnlyList hitObjects, IReadOnlyList randomObjects) - { - RandomObjectInfo? previous = null; - - for (int i = 0; i < hitObjects.Count; i++) - { - var hitObject = hitObjects[i]; - - var current = randomObjects[i]; - - if (hitObject is Spinner) - { - previous = null; - continue; - } - - computeRandomisedPosition(current, previous, i > 1 ? randomObjects[i - 2] : null); - - // Move hit objects back into the playfield if they are outside of it - Vector2 shift = Vector2.Zero; - - switch (hitObject) - { - case HitCircle circle: - shift = clampHitCircleToPlayfield(circle, current); - break; - - case Slider slider: - shift = clampSliderToPlayfield(slider, current); - break; - } - - if (shift != Vector2.Zero) - { - var toBeShifted = new List(); - - for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) - { - // only shift hit circles - if (!(hitObjects[j] is HitCircle)) break; - - toBeShifted.Add(hitObjects[j]); - } - - if (toBeShifted.Count > 0) - applyDecreasingShift(toBeShifted, shift); - } - - previous = current; - } - } - - /// - /// Compute the randomised position of a hit object while attempting to keep it inside the playfield. - /// - /// The representing the hit object to have the randomised position computed for. - /// The representing the hit object immediately preceding the current one. - /// The representing the hit object immediately preceding the one. - private void computeRandomisedPosition(RandomObjectInfo current, RandomObjectInfo? previous, RandomObjectInfo? beforePrevious) - { - float previousAbsoluteAngle = 0f; - - if (previous != null) - { - Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre; - Vector2 relativePosition = previous.HitObject.Position - earliestPosition; - previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); - } - - float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; - - var posRelativeToPrev = new Vector2( - current.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), - current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) - ); - - Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre; - - posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); - - current.PositionRandomised = lastEndPosition + posRelativeToPrev; - } - - /// - /// Move the randomised position of a hit circle so that it fits inside the playfield. - /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) - { - var previousPosition = objectInfo.PositionRandomised; - objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( - objectInfo.PositionRandomised, - (float)circle.Radius - ); - - circle.Position = objectInfo.PositionRandomised; - - return objectInfo.PositionRandomised - previousPosition; - } - - /// - /// Moves the and all necessary nested s into the if they aren't already. - /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) - { - var possibleMovementBounds = calculatePossibleMovementBounds(slider); - - var previousPosition = objectInfo.PositionRandomised; - - // Clamp slider position to the placement area - // If the slider is larger than the playfield, force it to stay at the original position - float newX = possibleMovementBounds.Width < 0 - ? objectInfo.PositionOriginal.X - : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); - - float newY = possibleMovementBounds.Height < 0 - ? objectInfo.PositionOriginal.Y - : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); - - slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); - objectInfo.EndPositionRandomised = slider.EndPosition; - - shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal); - - return objectInfo.PositionRandomised - previousPosition; - } - - /// - /// Decreasingly shift a list of s by a specified amount. - /// The first item in the list is shifted by the largest amount, while the last item is shifted by the smallest amount. - /// - /// The list of hit objects to be shifted. - /// The amount to be shifted. - private void applyDecreasingShift(IList hitObjects, Vector2 shift) - { - for (int i = 0; i < hitObjects.Count; i++) - { - var hitObject = hitObjects[i]; - // The first object is shifted by a vector slightly smaller than shift - // The last object is shifted by a vector slightly larger than zero - Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); - - hitObject.Position = clampToPlayfieldWithPadding(position, (float)hitObject.Radius); - } - } - - /// - /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) - /// such that the entire slider is inside the playfield. - /// - /// - /// If the slider is larger than the playfield, the returned may have negative width/height. - /// - private RectangleF calculatePossibleMovementBounds(Slider slider) - { - var pathPositions = new List(); - slider.Path.GetPathToProgress(pathPositions, 0, 1); - - float minX = float.PositiveInfinity; - float maxX = float.NegativeInfinity; - - float minY = float.PositiveInfinity; - float maxY = float.NegativeInfinity; - - // Compute the bounding box of the slider. - foreach (var pos in pathPositions) - { - minX = MathF.Min(minX, pos.X); - maxX = MathF.Max(maxX, pos.X); - - minY = MathF.Min(minY, pos.Y); - maxY = MathF.Max(maxY, pos.Y); - } - - // Take the circle radius into account. - float radius = (float)slider.Radius; - - minX -= radius; - minY -= radius; - - maxX += radius; - maxY += radius; - - // Given the bounding box of the slider (via min/max X/Y), - // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), - // and the amount that it can move to the right is WIDTH - maxX. - // Same calculation applies for the Y axis. - float left = -minX; - float right = OsuPlayfield.BASE_SIZE.X - maxX; - float top = -minY; - float bottom = OsuPlayfield.BASE_SIZE.Y - maxY; - - return new RectangleF(left, top, right - left, bottom - top); - } - - /// - /// Shifts all nested s and s by the specified shift. - /// - /// whose nested s and s should be shifted - /// The the 's nested s and s should be shifted by - private void shiftNestedObjects(Slider slider, Vector2 shift) - { - foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) - { - if (!(hitObject is OsuHitObject osuHitObject)) - continue; - - osuHitObject.Position += shift; - } - } - - /// - /// Clamp a position to playfield, keeping a specified distance from the edges. - /// - /// The position to be clamped. - /// The minimum distance allowed from playfield edges. - /// The clamped position. - private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) - { - return new Vector2( - Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), - Math.Clamp(position.Y, padding, OsuPlayfield.BASE_SIZE.Y - padding) - ); - } - - private class RandomObjectInfo - { - /// - /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. - /// - /// - /// of the first hit object in a beatmap represents the absolute angle from playfield center to the object. - /// - /// - /// If is 0, the player's cursor doesn't need to change its direction of movement when passing - /// the previous object to reach this one. - /// - public float RelativeAngle { get; set; } - - /// - /// The jump distance from the previous hit object to this one. - /// - /// - /// of the first hit object in a beatmap is relative to the playfield center. - /// - public float DistanceFromPrevious { get; set; } - - public Vector2 PositionOriginal { get; } - public Vector2 PositionRandomised { get; set; } - - public Vector2 EndPositionOriginal { get; } - public Vector2 EndPositionRandomised { get; set; } - - public OsuHitObject HitObject { get; } - - public RandomObjectInfo(OsuHitObject hitObject) - { - PositionRandomised = PositionOriginal = hitObject.Position; - EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; - HitObject = hitObject; - } + positionModifier.ApplyRandomisation(); } } } From 6a507ca11bdf4c95cba876be61bacbc4a9e1a9d5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 20:52:11 +0800 Subject: [PATCH 0021/1036] Rename identifiers to remove references to random mod --- .../Mods/OsuHitObjectPositionModifier.cs | 86 +++++++++---------- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 6 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs index 3242b99755..84ad198951 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { /// - /// Places hit objects according to information in while keeping objects inside the playfield. + /// Places hit objects according to information in while keeping objects inside the playfield. /// public class OsuHitObjectPositionModifier { @@ -28,21 +28,21 @@ namespace osu.Game.Rulesets.Osu.Mods private readonly List hitObjects; - private readonly List randomObjects = new List(); + private readonly List objectPositionInfos = new List(); /// /// Contains information specifying how each hit object should be placed. /// The default values correspond to how objects are originally placed in the beatmap. /// - public IReadOnlyList RandomObjects => randomObjects; + public IReadOnlyList ObjectPositionInfos => objectPositionInfos; public OsuHitObjectPositionModifier(List hitObjects) { this.hitObjects = hitObjects; - populateHitObjectPositions(); + populateObjectPositionInfos(); } - private void populateHitObjectPositions() + private void populateObjectPositionInfos() { Vector2 previousPosition = playfield_centre; float previousAngle = 0; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); float relativeAngle = absoluteAngle - previousAngle; - randomObjects.Add(new RandomObjectInfo(hitObject) + objectPositionInfos.Add(new ObjectPositionInfo(hitObject) { RelativeAngle = relativeAngle, DistanceFromPrevious = relativePosition.Length @@ -65,17 +65,17 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Reposition the hit objects according to the information in . + /// Reposition the hit objects according to the information in . /// - public void ApplyRandomisation() + public void ApplyModifications() { - RandomObjectInfo? previous = null; + ObjectPositionInfo? previous = null; for (int i = 0; i < hitObjects.Count; i++) { var hitObject = hitObjects[i]; - var current = randomObjects[i]; + var current = objectPositionInfos[i]; if (hitObject is Spinner) { @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods continue; } - computeRandomisedPosition(current, previous, i > 1 ? randomObjects[i - 2] : null); + computeModifiedPosition(current, previous, i > 1 ? objectPositionInfos[i - 2] : null); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -120,12 +120,12 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Compute the randomised position of a hit object while attempting to keep it inside the playfield. + /// Compute the modified position of a hit object while attempting to keep it inside the playfield. /// - /// The representing the hit object to have the randomised position computed for. - /// The representing the hit object immediately preceding the current one. - /// The representing the hit object immediately preceding the one. - private void computeRandomisedPosition(RandomObjectInfo current, RandomObjectInfo? previous, RandomObjectInfo? beforePrevious) + /// The representing the hit object to have the modified position computed for. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -143,56 +143,56 @@ namespace osu.Game.Rulesets.Osu.Mods current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) ); - Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre; + Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); - current.PositionRandomised = lastEndPosition + posRelativeToPrev; + current.PositionModified = lastEndPosition + posRelativeToPrev; } /// - /// Move the randomised position of a hit circle so that it fits inside the playfield. + /// Move the modified position of a hit circle so that it fits inside the playfield. /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) + /// The deviation from the original modified position in order to fit within the playfield. + private Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) { - var previousPosition = objectInfo.PositionRandomised; - objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( - objectInfo.PositionRandomised, + var previousPosition = objectPositionInfo.PositionModified; + objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( + objectPositionInfo.PositionModified, (float)circle.Radius ); - circle.Position = objectInfo.PositionRandomised; + circle.Position = objectPositionInfo.PositionModified; - return objectInfo.PositionRandomised - previousPosition; + return objectPositionInfo.PositionModified - previousPosition; } /// /// Moves the and all necessary nested s into the if they aren't already. /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) + /// The deviation from the original modified position in order to fit within the playfield. + private Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); - var previousPosition = objectInfo.PositionRandomised; + var previousPosition = objectPositionInfo.PositionModified; // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position float newX = possibleMovementBounds.Width < 0 - ? objectInfo.PositionOriginal.X + ? objectPositionInfo.PositionOriginal.X : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); float newY = possibleMovementBounds.Height < 0 - ? objectInfo.PositionOriginal.Y + ? objectPositionInfo.PositionOriginal.Y : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); - slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); - objectInfo.EndPositionRandomised = slider.EndPosition; + slider.Position = objectPositionInfo.PositionModified = new Vector2(newX, newY); + objectPositionInfo.EndPositionModified = slider.EndPosition; - shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal); + shiftNestedObjects(slider, objectPositionInfo.PositionModified - objectPositionInfo.PositionOriginal); - return objectInfo.PositionRandomised - previousPosition; + return objectPositionInfo.PositionModified - previousPosition; } /// @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Osu.Mods ); } - public interface IRandomObjectInfo + public interface IObjectPositionInfo { /// /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. @@ -316,29 +316,29 @@ namespace osu.Game.Rulesets.Osu.Mods float DistanceFromPrevious { get; set; } /// - /// The hit object associated with this . + /// The hit object associated with this . /// OsuHitObject HitObject { get; } } - private class RandomObjectInfo : IRandomObjectInfo + private class ObjectPositionInfo : IObjectPositionInfo { public float RelativeAngle { get; set; } public float DistanceFromPrevious { get; set; } public Vector2 PositionOriginal { get; } - public Vector2 PositionRandomised { get; set; } + public Vector2 PositionModified { get; set; } public Vector2 EndPositionOriginal { get; } - public Vector2 EndPositionRandomised { get; set; } + public Vector2 EndPositionModified { get; set; } public OsuHitObject HitObject { get; } - public RandomObjectInfo(OsuHitObject hitObject) + public ObjectPositionInfo(OsuHitObject hitObject) { - PositionRandomised = PositionOriginal = hitObject.Position; - EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; + PositionModified = PositionOriginal = hitObject.Position; + EndPositionModified = EndPositionOriginal = hitObject.EndPosition; HitObject = hitObject; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 2c38be6c16..59abc73ed9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -38,14 +38,14 @@ namespace osu.Game.Rulesets.Osu.Mods float rateOfChangeMultiplier = 0; - foreach (var positionInfo in positionModifier.RandomObjects) + foreach (var positionInfo in positionModifier.ObjectPositionInfos) { // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - if (positionInfo == positionModifier.RandomObjects.First()) + if (positionInfo == positionModifier.ObjectPositionInfos.First()) { positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2; positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - positionModifier.ApplyRandomisation(); + positionModifier.ApplyModifications(); } } } From 8e12a067dfb1695c984d1d4705aff15c5af18b8b Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 21:04:35 +0800 Subject: [PATCH 0022/1036] Remove an unused property --- osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs index 84ad198951..16ec25f389 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs @@ -329,8 +329,6 @@ namespace osu.Game.Rulesets.Osu.Mods public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } - - public Vector2 EndPositionOriginal { get; } public Vector2 EndPositionModified { get; set; } public OsuHitObject HitObject { get; } @@ -338,7 +336,7 @@ namespace osu.Game.Rulesets.Osu.Mods public ObjectPositionInfo(OsuHitObject hitObject) { PositionModified = PositionOriginal = hitObject.Position; - EndPositionModified = EndPositionOriginal = hitObject.EndPosition; + EndPositionModified = hitObject.EndPosition; HitObject = hitObject; } } From e8dbed738e4b819f5a68bf9b1df6a2d1b2a824a7 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 21:52:15 +0800 Subject: [PATCH 0023/1036] Move `OsuHitObjectPositionModifier` to `Utils/` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 1 + .../{Mods => Utils}/OsuHitObjectPositionModifier.cs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/{Mods => Utils}/OsuHitObjectPositionModifier.cs (99%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 59abc73ed9..cdaa8fa3d5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; namespace osu.Game.Rulesets.Osu.Mods { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs similarity index 99% rename from osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs rename to osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs index 16ec25f389..428866623f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs @@ -7,12 +7,11 @@ using System.Linq; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Osu.Utils; using osuTK; #nullable enable -namespace osu.Game.Rulesets.Osu.Mods +namespace osu.Game.Rulesets.Osu.Utils { /// /// Places hit objects according to information in while keeping objects inside the playfield. From ede838c4b3ebaa2fa2471de68ec11bbf7fbd21e5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Mar 2022 11:23:52 +0800 Subject: [PATCH 0024/1036] Use `ObjectPositionInfo.HitObject` --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs index 428866623f..32f547dfe7 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs @@ -70,11 +70,10 @@ namespace osu.Game.Rulesets.Osu.Utils { ObjectPositionInfo? previous = null; - for (int i = 0; i < hitObjects.Count; i++) + for (int i = 0; i < objectPositionInfos.Count; i++) { - var hitObject = hitObjects[i]; - var current = objectPositionInfos[i]; + var hitObject = current.HitObject; if (hitObject is Spinner) { From 3a71d817758e8387568a0a8f974f6c8ac22e43e7 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Mar 2022 11:53:03 +0800 Subject: [PATCH 0025/1036] Convert the position modifier to stateless methods --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 8 +-- .../Utils/OsuHitObjectGenerationUtils.cs | 2 +- ...OsuHitObjectGenerationUtils_Reposition.cs} | 65 +++++++++---------- 3 files changed, 35 insertions(+), 40 deletions(-) rename osu.Game.Rulesets.Osu/Utils/{OsuHitObjectPositionModifier.cs => OsuHitObjectGenerationUtils_Reposition.cs} (84%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index cdaa8fa3d5..3c2c5d7759 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -35,18 +35,18 @@ namespace osu.Game.Rulesets.Osu.Mods rng = new Random((int)Seed.Value); - var positionModifier = new OsuHitObjectPositionModifier(osuBeatmap.HitObjects); + var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); float rateOfChangeMultiplier = 0; - foreach (var positionInfo in positionModifier.ObjectPositionInfos) + foreach (var positionInfo in positionInfos) { // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - if (positionInfo == positionModifier.ObjectPositionInfos.First()) + if (positionInfo == positionInfos.First()) { positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2; positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - positionModifier.ApplyModifications(); + osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos); } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 97a4b14a62..da73c2addb 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class OsuHitObjectGenerationUtils + public static partial class OsuHitObjectGenerationUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs similarity index 84% rename from osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs rename to osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 32f547dfe7..2a735c89d9 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -13,10 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - /// - /// Places hit objects according to information in while keeping objects inside the playfield. - /// - public class OsuHitObjectPositionModifier + public static partial class OsuHitObjectGenerationUtils { /// /// Number of previous hitobjects to be shifted together when an object is being moved. @@ -25,24 +22,15 @@ namespace osu.Game.Rulesets.Osu.Utils private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; - private readonly List hitObjects; - - private readonly List objectPositionInfos = new List(); - /// - /// Contains information specifying how each hit object should be placed. - /// The default values correspond to how objects are originally placed in the beatmap. + /// Generate a list of s containing information for how the given list of + /// s are positioned. /// - public IReadOnlyList ObjectPositionInfos => objectPositionInfos; - - public OsuHitObjectPositionModifier(List hitObjects) - { - this.hitObjects = hitObjects; - populateObjectPositionInfos(); - } - - private void populateObjectPositionInfos() + /// A list of s to process. + /// A list of s describing how each hit object is positioned relative to the previous one. + public static List GeneratePositionInfos(IEnumerable hitObjects) { + var positionInfos = new List(); Vector2 previousPosition = playfield_centre; float previousAngle = 0; @@ -52,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Utils float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); float relativeAngle = absoluteAngle - previousAngle; - objectPositionInfos.Add(new ObjectPositionInfo(hitObject) + positionInfos.Add(new ObjectPositionInfo(hitObject) { RelativeAngle = relativeAngle, DistanceFromPrevious = relativePosition.Length @@ -61,18 +49,23 @@ namespace osu.Game.Rulesets.Osu.Utils previousPosition = hitObject.EndPosition; previousAngle = absoluteAngle; } + + return positionInfos; } /// - /// Reposition the hit objects according to the information in . + /// Reposition the hit objects according to the information in . /// - public void ApplyModifications() + /// + /// The repositioned hit objects. + public static List RepositionHitObjects(IEnumerable objectPositionInfos) { + List positionInfos = objectPositionInfos.Cast().ToList(); ObjectPositionInfo? previous = null; - for (int i = 0; i < objectPositionInfos.Count; i++) + for (int i = 0; i < positionInfos.Count; i++) { - var current = objectPositionInfos[i]; + var current = positionInfos[i]; var hitObject = current.HitObject; if (hitObject is Spinner) @@ -81,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Utils continue; } - computeModifiedPosition(current, previous, i > 1 ? objectPositionInfos[i - 2] : null); + computeModifiedPosition(current, previous, i > 1 ? positionInfos[i - 2] : null); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -104,9 +97,9 @@ namespace osu.Game.Rulesets.Osu.Utils for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) { // only shift hit circles - if (!(hitObjects[j] is HitCircle)) break; + if (!(positionInfos[j].HitObject is HitCircle)) break; - toBeShifted.Add(hitObjects[j]); + toBeShifted.Add(positionInfos[j].HitObject); } if (toBeShifted.Count > 0) @@ -115,6 +108,8 @@ namespace osu.Game.Rulesets.Osu.Utils previous = current; } + + return positionInfos.Select(p => p.HitObject).ToList(); } /// @@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// The representing the hit object to have the modified position computed for. /// The representing the hit object immediately preceding the current one. /// The representing the hit object immediately preceding the one. - private void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) + private static void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -143,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Utils Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; - posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); + posRelativeToPrev = RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); current.PositionModified = lastEndPosition + posRelativeToPrev; } @@ -152,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Move the modified position of a hit circle so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) { var previousPosition = objectPositionInfo.PositionModified; objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( @@ -169,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Moves the and all necessary nested s into the if they aren't already. /// /// The deviation from the original modified position in order to fit within the playfield. - private Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); @@ -199,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// The list of hit objects to be shifted. /// The amount to be shifted. - private void applyDecreasingShift(IList hitObjects, Vector2 shift) + private static void applyDecreasingShift(IList hitObjects, Vector2 shift) { for (int i = 0; i < hitObjects.Count; i++) { @@ -219,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - private RectangleF calculatePossibleMovementBounds(Slider slider) + private static RectangleF calculatePossibleMovementBounds(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -266,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// whose nested s and s should be shifted /// The the 's nested s and s should be shifted by - private void shiftNestedObjects(Slider slider, Vector2 shift) + private static void shiftNestedObjects(Slider slider, Vector2 shift) { foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) { @@ -283,7 +278,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// The position to be clamped. /// The minimum distance allowed from playfield edges. /// The clamped position. - private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) + private static Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) { return new Vector2( Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), From 5e36383258b8a5cb01ab6298d6d2283ef86e6c4f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Mar 2022 12:02:25 +0800 Subject: [PATCH 0026/1036] Convert `IObjectPositionInfo` to a class --- .../OsuHitObjectGenerationUtils_Reposition.cs | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 2a735c89d9..37a12b20b4 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -23,14 +23,14 @@ namespace osu.Game.Rulesets.Osu.Utils private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; /// - /// Generate a list of s containing information for how the given list of + /// Generate a list of s containing information for how the given list of /// s are positioned. /// /// A list of s to process. - /// A list of s describing how each hit object is positioned relative to the previous one. - public static List GeneratePositionInfos(IEnumerable hitObjects) + /// A list of s describing how each hit object is positioned relative to the previous one. + public static List GeneratePositionInfos(IEnumerable hitObjects) { - var positionInfos = new List(); + var positionInfos = new List(); Vector2 previousPosition = playfield_centre; float previousAngle = 0; @@ -56,12 +56,12 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// Reposition the hit objects according to the information in . /// - /// + /// Position information for each hit object. /// The repositioned hit objects. - public static List RepositionHitObjects(IEnumerable objectPositionInfos) + public static List RepositionHitObjects(IEnumerable objectPositionInfos) { - List positionInfos = objectPositionInfos.Cast().ToList(); - ObjectPositionInfo? previous = null; + List positionInfos = objectPositionInfos.Select(o => new ObjectPositionInfoInternal(o)).ToList(); + ObjectPositionInfoInternal? previous = null; for (int i = 0; i < positionInfos.Count; i++) { @@ -115,10 +115,10 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// Compute the modified position of a hit object while attempting to keep it inside the playfield. /// - /// The representing the hit object to have the modified position computed for. - /// The representing the hit object immediately preceding the current one. - /// The representing the hit object immediately preceding the one. - private static void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) + /// The representing the hit object to have the modified position computed for. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private static void computeModifiedPosition(ObjectPositionInfoInternal current, ObjectPositionInfoInternal? previous, ObjectPositionInfoInternal? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Move the modified position of a hit circle so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfoInternal objectPositionInfo) { var previousPosition = objectPositionInfo.PositionModified; objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Moves the and all necessary nested s into the if they aren't already. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfoInternal objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); @@ -286,7 +286,7 @@ namespace osu.Game.Rulesets.Osu.Utils ); } - public interface IObjectPositionInfo + public class ObjectPositionInfo { /// /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. @@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// If is 0, the player's cursor doesn't need to change its direction of movement when passing /// the previous object to reach this one. /// - float RelativeAngle { get; set; } + public float RelativeAngle { get; set; } /// /// The jump distance from the previous hit object to this one. @@ -306,32 +306,33 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// of the first hit object in a beatmap is relative to the playfield center. /// - float DistanceFromPrevious { get; set; } - - /// - /// The hit object associated with this . - /// - OsuHitObject HitObject { get; } - } - - private class ObjectPositionInfo : IObjectPositionInfo - { - public float RelativeAngle { get; set; } - public float DistanceFromPrevious { get; set; } - public Vector2 PositionOriginal { get; } - public Vector2 PositionModified { get; set; } - public Vector2 EndPositionModified { get; set; } - + /// + /// The hit object associated with this . + /// public OsuHitObject HitObject { get; } public ObjectPositionInfo(OsuHitObject hitObject) { - PositionModified = PositionOriginal = hitObject.Position; - EndPositionModified = hitObject.EndPosition; HitObject = hitObject; } } + + private class ObjectPositionInfoInternal : ObjectPositionInfo + { + public Vector2 PositionOriginal { get; } + public Vector2 PositionModified { get; set; } + public Vector2 EndPositionModified { get; set; } + + public ObjectPositionInfoInternal(ObjectPositionInfo original) + : base(original.HitObject) + { + RelativeAngle = original.RelativeAngle; + DistanceFromPrevious = original.DistanceFromPrevious; + PositionModified = PositionOriginal = HitObject.Position; + EndPositionModified = HitObject.EndPosition; + } + } } } From 6657d93b29e2dbeda325333c674fd53c572b0d66 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 14 Mar 2022 20:18:30 +0800 Subject: [PATCH 0027/1036] Separate the two nested classes --- .../OsuHitObjectGenerationUtils_Reposition.cs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 37a12b20b4..94f4f154bd 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Osu.Utils /// The repositioned hit objects. public static List RepositionHitObjects(IEnumerable objectPositionInfos) { - List positionInfos = objectPositionInfos.Select(o => new ObjectPositionInfoInternal(o)).ToList(); - ObjectPositionInfoInternal? previous = null; + List workingObjects = objectPositionInfos.Select(o => new WorkingObject(o)).ToList(); + WorkingObject? previous = null; - for (int i = 0; i < positionInfos.Count; i++) + for (int i = 0; i < workingObjects.Count; i++) { - var current = positionInfos[i]; + var current = workingObjects[i]; var hitObject = current.HitObject; if (hitObject is Spinner) @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Utils continue; } - computeModifiedPosition(current, previous, i > 1 ? positionInfos[i - 2] : null); + computeModifiedPosition(current, previous, i > 1 ? workingObjects[i - 2] : null); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -97,9 +97,9 @@ namespace osu.Game.Rulesets.Osu.Utils for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) { // only shift hit circles - if (!(positionInfos[j].HitObject is HitCircle)) break; + if (!(workingObjects[j].HitObject is HitCircle)) break; - toBeShifted.Add(positionInfos[j].HitObject); + toBeShifted.Add(workingObjects[j].HitObject); } if (toBeShifted.Count > 0) @@ -109,16 +109,16 @@ namespace osu.Game.Rulesets.Osu.Utils previous = current; } - return positionInfos.Select(p => p.HitObject).ToList(); + return workingObjects.Select(p => p.HitObject).ToList(); } /// /// Compute the modified position of a hit object while attempting to keep it inside the playfield. /// - /// The representing the hit object to have the modified position computed for. - /// The representing the hit object immediately preceding the current one. - /// The representing the hit object immediately preceding the one. - private static void computeModifiedPosition(ObjectPositionInfoInternal current, ObjectPositionInfoInternal? previous, ObjectPositionInfoInternal? beforePrevious) + /// The representing the hit object to have the modified position computed for. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private static void computeModifiedPosition(WorkingObject current, WorkingObject? previous, WorkingObject? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -129,11 +129,11 @@ namespace osu.Game.Rulesets.Osu.Utils previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); } - float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; + float absoluteAngle = previousAbsoluteAngle + current.PositionInfo.RelativeAngle; var posRelativeToPrev = new Vector2( - current.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), - current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) + current.PositionInfo.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), + current.PositionInfo.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) ); Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Move the modified position of a hit circle so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfoInternal objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(HitCircle circle, WorkingObject objectPositionInfo) { var previousPosition = objectPositionInfo.PositionModified; objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Moves the and all necessary nested s into the if they aren't already. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfoInternal objectPositionInfo) + private static Vector2 clampSliderToPlayfield(Slider slider, WorkingObject objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); @@ -319,17 +319,18 @@ namespace osu.Game.Rulesets.Osu.Utils } } - private class ObjectPositionInfoInternal : ObjectPositionInfo + private class WorkingObject { public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } public Vector2 EndPositionModified { get; set; } - public ObjectPositionInfoInternal(ObjectPositionInfo original) - : base(original.HitObject) + public ObjectPositionInfo PositionInfo { get; } + public OsuHitObject HitObject => PositionInfo.HitObject; + + public WorkingObject(ObjectPositionInfo positionInfo) { - RelativeAngle = original.RelativeAngle; - DistanceFromPrevious = original.DistanceFromPrevious; + PositionInfo = positionInfo; PositionModified = PositionOriginal = HitObject.Position; EndPositionModified = HitObject.EndPosition; } From 76021c76278cf3a91a6f38e6b9b047a93f5dadad Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 14 Mar 2022 20:23:35 +0800 Subject: [PATCH 0028/1036] Remove extra parameters --- .../OsuHitObjectGenerationUtils_Reposition.cs | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 94f4f154bd..d1bc3b45df 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -81,12 +81,12 @@ namespace osu.Game.Rulesets.Osu.Utils switch (hitObject) { - case HitCircle circle: - shift = clampHitCircleToPlayfield(circle, current); + case HitCircle _: + shift = clampHitCircleToPlayfield(current); break; - case Slider slider: - shift = clampSliderToPlayfield(slider, current); + case Slider _: + shift = clampSliderToPlayfield(current); break; } @@ -144,48 +144,49 @@ namespace osu.Game.Rulesets.Osu.Utils } /// - /// Move the modified position of a hit circle so that it fits inside the playfield. + /// Move the modified position of a so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampHitCircleToPlayfield(HitCircle circle, WorkingObject objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(WorkingObject workingObject) { - var previousPosition = objectPositionInfo.PositionModified; - objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( - objectPositionInfo.PositionModified, - (float)circle.Radius + var previousPosition = workingObject.PositionModified; + workingObject.EndPositionModified = workingObject.PositionModified = clampToPlayfieldWithPadding( + workingObject.PositionModified, + (float)workingObject.HitObject.Radius ); - circle.Position = objectPositionInfo.PositionModified; + workingObject.HitObject.Position = workingObject.PositionModified; - return objectPositionInfo.PositionModified - previousPosition; + return workingObject.PositionModified - previousPosition; } /// /// Moves the and all necessary nested s into the if they aren't already. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampSliderToPlayfield(Slider slider, WorkingObject objectPositionInfo) + private static Vector2 clampSliderToPlayfield(WorkingObject workingObject) { + var slider = (Slider)workingObject.HitObject; var possibleMovementBounds = calculatePossibleMovementBounds(slider); - var previousPosition = objectPositionInfo.PositionModified; + var previousPosition = workingObject.PositionModified; // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position float newX = possibleMovementBounds.Width < 0 - ? objectPositionInfo.PositionOriginal.X + ? workingObject.PositionOriginal.X : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); float newY = possibleMovementBounds.Height < 0 - ? objectPositionInfo.PositionOriginal.Y + ? workingObject.PositionOriginal.Y : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); - slider.Position = objectPositionInfo.PositionModified = new Vector2(newX, newY); - objectPositionInfo.EndPositionModified = slider.EndPosition; + slider.Position = workingObject.PositionModified = new Vector2(newX, newY); + workingObject.EndPositionModified = slider.EndPosition; - shiftNestedObjects(slider, objectPositionInfo.PositionModified - objectPositionInfo.PositionOriginal); + shiftNestedObjects(slider, workingObject.PositionModified - workingObject.PositionOriginal); - return objectPositionInfo.PositionModified - previousPosition; + return workingObject.PositionModified - previousPosition; } /// From 39c30516d0874b025d3e8e0610bd6ab096ebd2a6 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 18:31:13 +0000 Subject: [PATCH 0029/1036] Implement `ChannelControlItem` for new chat design Adds new component `ChannelControlItem` and it's child components to be used as the clickable control in the new chat sidebar for joined channels. Has public properties `HasUnread` and `MentionCount` to control the display of the channel having unread messages or mentions of the user. Channel select/join requests are exposed via `OnRequestSelect` and `OnRequestLeave` events respectively which should be handled by a parent component. Requires a cached `Bindable` instance to be managed by a parent component. Requires a cached `OveralayColourScheme` instance to be provided by a parent component. --- .../Online/TestSceneChannelControlItem.cs | 145 +++++++++++++++ .../Chat/ChannelControl/ControlItem.cs | 167 ++++++++++++++++++ .../Chat/ChannelControl/ControlItemAvatar.cs | 60 +++++++ .../Chat/ChannelControl/ControlItemClose.cs | 55 ++++++ .../Chat/ChannelControl/ControlItemMention.cs | 77 ++++++++ .../Chat/ChannelControl/ControlItemText.cs | 71 ++++++++ 6 files changed, 575 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs new file mode 100644 index 0000000000..a1431f696b --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -0,0 +1,145 @@ +// 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 System.Collections.Generic; +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.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.Chat.ChannelControl; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneChannelControlItem : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + [Cached] + private readonly Bindable selected = new Bindable(); + + private static List channels = new List + { + createPublicChannel("#public-channel"), + createPublicChannel("#public-channel-long-name"), + createPrivateChannel("test user", 2), + createPrivateChannel("test user long name", 3), + }; + + private readonly Dictionary channelMap = new Dictionary(); + + private FillFlowContainer flow; + private OsuSpriteText selectedText; + private OsuSpriteText leaveText; + + [SetUp] + public void SetUp() + { + Schedule(() => + { + Children = new Drawable[] + { + selectedText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -140, + }, + leaveText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -120, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(190, 200), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + flow = new FillFlowContainer + { + Spacing = new Vector2(20), + RelativeSizeAxes = Axes.Both, + }, + }, + }, + }; + + selected.BindValueChanged(change => + { + selectedText.Text = $"Selected Channel: {change.NewValue?.Name ?? "[null]"}"; + }, true); + + foreach (var channel in channels) + { + var item = new ControlItem(channel); + flow.Add(item); + channelMap.Add(channel, item); + item.OnRequestSelect += channel => selected.Value = channel; + item.OnRequestLeave += leaveChannel; + } + }); + } + + [Test] + public void TestVisual() + { + AddStep("Unread Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].HasUnread = true; + }); + + AddStep("Read Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].HasUnread = false; + }); + + AddStep("Add Mention Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount++; + }); + + AddStep("Add 98 Mentions Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount += 98; + }); + + AddStep("Clear Mentions Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount = 0; + }); + } + + private void leaveChannel(Channel channel) + { + leaveText.Text = $"OnRequestLeave: {channel.Name}"; + leaveText.FadeIn().Then().FadeOut(1000, Easing.InQuint); + } + + private static Channel createPublicChannel(string name) => + new Channel { Name = name, Type = ChannelType.Public, Id = 1234 }; + + private static Channel createPrivateChannel(string username, int id) + => new Channel(new APIUser { Id = id, Username = username }); + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs new file mode 100644 index 0000000000..559f75f198 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -0,0 +1,167 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItem : OsuClickableContainer + { + public event Action? OnRequestSelect; + public event Action? OnRequestLeave; + + public int MentionCount + { + get => mention?.MentionCount ?? 0; + set + { + if (mention != null) + mention.MentionCount = value; + } + } + + public bool HasUnread + { + get => text?.HasUnread ?? false; + set + { + if (text != null) + text.HasUnread = value; + } + } + + private Box? hoverBox; + private Box? selectBox; + private ControlItemText? text; + private ControlItemMention? mention; + private ControlItemClose? close; + + [Resolved] + private Bindable? selectedChannel { get; set; } + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + private readonly Channel channel; + + public ControlItem(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + Height = 30; + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + hoverBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Background3, + Alpha = 0f, + }, + selectBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Background4, + Alpha = 0f, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 18, Right = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + createAvatar(), + text = new ControlItemText(channel) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + mention = new ControlItemMention + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + }, + close = new ControlItemClose + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + } + } + }, + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedChannel?.BindValueChanged(change => + { + if (change.NewValue == channel) + selectBox?.Show(); + else + selectBox?.Hide(); + }, true); + + Action = () => OnRequestSelect?.Invoke(channel); + close!.Action = () => OnRequestLeave?.Invoke(channel); + } + + protected override bool OnHover(HoverEvent e) + { + hoverBox?.Show(); + close?.Show(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverBox?.Hide(); + close?.Hide(); + base.OnHoverLost(e); + } + + private Drawable createAvatar() + { + if (channel.Type != ChannelType.PM) + return Drawable.Empty(); + + return new ControlItemAvatar(channel) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs new file mode 100644 index 0000000000..9192f20cb1 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Users.Drawables; +using osuTK; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemAvatar : CircularContainer + { + private DrawableAvatar? avatar; + private readonly Channel channel; + + public ControlItemAvatar(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(20); + Margin = new MarginPadding { Right = 5 }; + Masking = true; + + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.At, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Colour4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + }, + new DelayedLoadWrapper(avatar = new DrawableAvatar(channel.Users.First()) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + avatar!.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs new file mode 100644 index 0000000000..4b190e18fd --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs @@ -0,0 +1,55 @@ +// 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.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemClose : OsuClickableContainer + { + private readonly SpriteIcon icon; + + public ControlItemClose() + { + Alpha = 0f; + Size = new Vector2(20); + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.75f), + Icon = FontAwesome.Solid.TimesCircle, + RelativeSizeAxes = Axes.Both, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + icon.ScaleTo(0.5f, 1000, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + icon.ScaleTo(0.75f, 1000, Easing.OutElastic); + base.OnMouseUp(e); + } + + protected override bool OnHover(HoverEvent e) + { + icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + icon.FadeColour(Color4.White, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs new file mode 100644 index 0000000000..12de154faa --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemMention : CircularContainer + { + private int mentionCount = 0; + public int MentionCount + { + get => mentionCount; + set + { + if (value == mentionCount) + return; + + mentionCount = value; + updateText(); + } + } + + private OsuSpriteText? countText; + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Masking = true; + Size = new Vector2(20, 12); + Alpha = 0f; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Colour1, + }, + countText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 11, weight: FontWeight.Bold), + Margin = new MarginPadding { Bottom = 1 }, + Colour = colourProvider.Background5, + }, + }; + + updateText(); + } + + private void updateText() + { + if (mentionCount > 99) + countText!.Text = "99+"; + else + countText!.Text = mentionCount.ToString(); + + if (mentionCount > 0) + this.Show(); + else + this.Hide(); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs new file mode 100644 index 0000000000..2b8f50e184 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +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.Online.Chat; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemText : Container + { + public bool HasUnread + { + get => hasUnread; + set + { + if (hasUnread == value) + return; + + hasUnread = value; + updateText(); + } + } + + private bool hasUnread = false; + private OsuSpriteText? text; + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + private readonly Channel channel; + + public ControlItemText(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + Child = text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = channel.Type == ChannelType.Public ? $"# {channel.Name.Substring(1)}" : channel.Name, + Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), + Colour = colourProvider!.Light3, + Margin = new MarginPadding { Bottom = 2 }, + RelativeSizeAxes = Axes.X, + Truncate = true, + }; + } + + private void updateText() + { + if (!IsLoaded) + return; + + if (HasUnread) + text!.Colour = Colour4.White; + else + text!.Colour = colourProvider!.Light3; + } + } +} From c0d82dfb41478a97e633a60fe847930bf85bf7ca Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 19:42:55 +0000 Subject: [PATCH 0030/1036] Code quality fixes --- .../Visual/Online/TestSceneChannelControlItem.cs | 4 ++-- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- .../Chat/ChannelControl/ControlItemMention.cs | 12 +++++------- .../Overlays/Chat/ChannelControl/ControlItemText.cs | 3 ++- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index a1431f696b..64ad924ecb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly Bindable selected = new Bindable(); - private static List channels = new List + private static readonly List channels = new List { createPublicChannel("#public-channel"), createPublicChannel("#public-channel-long-name"), @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online var item = new ControlItem(channel); flow.Add(item); channelMap.Add(channel, item); - item.OnRequestSelect += channel => selected.Value = channel; + item.OnRequestSelect += c => selected.Value = c; item.OnRequestLeave += leaveChannel; } }); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 559f75f198..e475ac7821 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl }, Content = new[] { - new Drawable[] + new[] { createAvatar(), text = new ControlItemText(channel) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 12de154faa..6af2e26af8 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -15,7 +15,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private int mentionCount = 0; + private int mentionCount; + public int MentionCount { get => mentionCount; @@ -63,15 +64,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl private void updateText() { - if (mentionCount > 99) - countText!.Text = "99+"; - else - countText!.Text = mentionCount.ToString(); + countText!.Text = MentionCount > 99 ? "99+" : MentionCount.ToString(); if (mentionCount > 0) - this.Show(); + Show(); else - this.Hide(); + Hide(); } } } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 2b8f50e184..bb88d733d4 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -14,6 +14,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { + private bool hasUnread; + public bool HasUnread { get => hasUnread; @@ -27,7 +29,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } - private bool hasUnread = false; private OsuSpriteText? text; [Resolved] From e9f0ad33efca61ccbbb0a09c14f88615d3e2f819 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:06:31 +0000 Subject: [PATCH 0031/1036] Use autosizing for ChannelControlItem test scene --- .../Online/TestSceneChannelControlItem.cs | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 64ad924ecb..7f76dca013 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -45,36 +45,47 @@ namespace osu.Game.Tests.Visual.Online { Schedule(() => { - Children = new Drawable[] + Child = new FillFlowContainer { - selectedText = new OsuSpriteText + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -140, - }, - leaveText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -120, - }, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(190, 200), - Children = new Drawable[] + selectedText = new OsuSpriteText { - new Box + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + leaveText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 16, + AlwaysPresent = true, + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + Width = 190, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background6, - }, - flow = new FillFlowContainer - { - Spacing = new Vector2(20), - RelativeSizeAxes = Axes.Both, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + flow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + Spacing = new Vector2(20), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, }, }, }, From 12472593cc4f2c7b36a8682c7d63d17315e7a9cf Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:14:04 +0000 Subject: [PATCH 0032/1036] Mark required dependencies as non-nullable --- .../Overlays/Chat/ChannelControl/ControlItem.cs | 14 +++++++------- .../Chat/ChannelControl/ControlItemAvatar.cs | 3 ++- .../Chat/ChannelControl/ControlItemMention.cs | 4 ++-- .../Chat/ChannelControl/ControlItemText.cs | 8 ++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index e475ac7821..073173f2ff 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -40,6 +40,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } + private readonly Channel channel; + private Box? hoverBox; private Box? selectBox; private ControlItemText? text; @@ -47,12 +49,10 @@ namespace osu.Game.Overlays.Chat.ChannelControl private ControlItemClose? close; [Resolved] - private Bindable? selectedChannel { get; set; } + private Bindable selectedChannel { get; set; } = null!; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } - - private readonly Channel channel; + private OverlayColourProvider colourProvider { get; set; } = null!; public ControlItem(Channel channel) { @@ -70,13 +70,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl hoverBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Background3, + Colour = colourProvider.Background3, Alpha = 0f, }, selectBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Background4, + Colour = colourProvider.Background4, Alpha = 0f, }, new Container @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - selectedChannel?.BindValueChanged(change => + selectedChannel.BindValueChanged(change => { if (change.NewValue == channel) selectBox?.Show(); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs index 9192f20cb1..62b893f3bc 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs @@ -16,9 +16,10 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemAvatar : CircularContainer { - private DrawableAvatar? avatar; private readonly Channel channel; + private DrawableAvatar? avatar; + public ControlItemAvatar(Channel channel) { this.channel = channel; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 6af2e26af8..693fb68217 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Colour1, + Colour = colourProvider.Colour1, }, countText = new OsuSpriteText { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index bb88d733d4..9b0f5011fc 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -29,12 +29,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } + private readonly Channel channel; + private OsuSpriteText? text; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } - - private readonly Channel channel; + private OverlayColourProvider colourProvider { get; set; } = null!; public ControlItemText(Channel channel) { @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl Origin = Anchor.CentreLeft, Text = channel.Type == ChannelType.Public ? $"# {channel.Name.Substring(1)}" : channel.Name, Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), - Colour = colourProvider!.Light3, + Colour = colourProvider.Light3, Margin = new MarginPadding { Bottom = 2 }, RelativeSizeAxes = Axes.X, Truncate = true, From e91af664ef080c250fbfd6997dd5aa1033f502d1 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:37:54 +0000 Subject: [PATCH 0033/1036] Adjust ControlItemAvatar placeholder animation and colour --- .../Overlays/Chat/ChannelControl/ControlItemAvatar.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs index 62b893f3bc..bb3109ec1f 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs @@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { private readonly Channel channel; + private SpriteIcon? placeholder; private DrawableAvatar? avatar; public ControlItemAvatar(Channel channel) @@ -34,14 +35,14 @@ namespace osu.Game.Overlays.Chat.ChannelControl Children = new Drawable[] { - new SpriteIcon + placeholder = new SpriteIcon { Icon = FontAwesome.Solid.At, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = Colour4.Black, + Colour = Colour4.White, RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, + Alpha = 0.5f, }, new DelayedLoadWrapper(avatar = new DrawableAvatar(channel.Users.First()) { @@ -55,7 +56,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl protected override void LoadComplete() { base.LoadComplete(); - avatar!.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + avatar!.OnLoadComplete += _ => placeholder!.FadeOut(250); } } } From 9621ef9437ca389fde4295e507d8681ee14ee3bb Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:10:39 +0000 Subject: [PATCH 0034/1036] Use OsuColour.Red1 for ControlItemClose hover colour --- .../Visual/Online/TestSceneChannelControlItem.cs | 4 ++++ .../Overlays/Chat/ChannelControl/ControlItemClose.cs | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 7f76dca013..9464d244be 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -8,6 +8,7 @@ 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.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + [Cached] + private readonly OsuColour osuColour = new OsuColour(); + [Cached] private readonly Bindable selected = new Bindable(); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs index 4b190e18fd..4730d7b72d 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs @@ -1,12 +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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Chat.ChannelControl { @@ -14,6 +15,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl { private readonly SpriteIcon icon; + [Resolved] + private OsuColour osuColour { get; set; } = null!; + public ControlItemClose() { Alpha = 0f; @@ -42,13 +46,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl protected override bool OnHover(HoverEvent e) { - icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + icon.FadeColour(osuColour.Red1, 200, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - icon.FadeColour(Color4.White, 200, Easing.OutQuint); + icon.FadeColour(Colour4.White, 200, Easing.OutQuint); base.OnHoverLost(e); } } From 3aa343b9870dc3487013151889880f282b29cafe Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:14:33 +0000 Subject: [PATCH 0035/1036] Use OsuColour.YellowLight for ControlItemMention background --- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 693fb68217..370c435266 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -32,6 +32,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; + [Resolved] + private OsuColour osuColour { get; set; } = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -47,7 +50,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Colour1, + Colour = osuColour.YellowLight, }, countText = new OsuSpriteText { From 1f0f6990f096d15a064eda731bd2dbad876ff786 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:16:28 +0000 Subject: [PATCH 0036/1036] Use ColourProvider.Content1 for ControlItemText colour --- osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 9b0f5011fc..0c91903f6b 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -64,9 +64,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl return; if (HasUnread) - text!.Colour = Colour4.White; + text!.Colour = colourProvider.Content1; else - text!.Colour = colourProvider!.Light3; + text!.Colour = colourProvider.Light3; } } } From b01a809d551efcb342e59046ae087d46309323fe Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:26:33 +0000 Subject: [PATCH 0037/1036] Refactor ControlItemMention to use bindable flow --- .../Online/TestSceneChannelControlItem.cs | 6 +-- .../Chat/ChannelControl/ControlItem.cs | 11 +---- .../Chat/ChannelControl/ControlItemMention.cs | 41 ++++++++----------- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 9464d244be..2e5c2cc2cb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -129,19 +129,19 @@ namespace osu.Game.Tests.Visual.Online AddStep("Add Mention Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount++; + channelMap[selected.Value].Mentions.Value++; }); AddStep("Add 98 Mentions Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount += 98; + channelMap[selected.Value].Mentions.Value += 98; }); AddStep("Clear Mentions Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount = 0; + channelMap[selected.Value].Mentions.Value = 0; }); } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 073173f2ff..d88a99c484 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -20,15 +20,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl public event Action? OnRequestSelect; public event Action? OnRequestLeave; - public int MentionCount - { - get => mention?.MentionCount ?? 0; - set - { - if (mention != null) - mention.MentionCount = value; - } - } + [Cached] + public readonly BindableInt Mentions = new BindableInt(); public bool HasUnread { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 370c435266..594a52b8a7 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -4,6 +4,7 @@ #nullable enable using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -15,23 +16,11 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private int mentionCount; - - public int MentionCount - { - get => mentionCount; - set - { - if (value == mentionCount) - return; - - mentionCount = value; - updateText(); - } - } - private OsuSpriteText? countText; + [Resolved] + private BindableInt mentions { get; set; } = null!; + [Resolved] private OsuColour osuColour { get; set; } = null!; @@ -61,18 +50,24 @@ namespace osu.Game.Overlays.Chat.ChannelControl Colour = colourProvider.Background5, }, }; - - updateText(); } - private void updateText() + protected override void LoadComplete() { - countText!.Text = MentionCount > 99 ? "99+" : MentionCount.ToString(); + base.LoadComplete(); - if (mentionCount > 0) - Show(); - else - Hide(); + mentions.BindValueChanged(change => + { + int mentionCount = change.NewValue; + + countText!.Text = mentionCount > 99 ? "99+" : mentionCount.ToString(); + + if (mentionCount > 0) + Show(); + else + Hide(); + }, true); } + } } From 75958bf2702778784caee4c70ed3040170f16a5e Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:32:30 +0000 Subject: [PATCH 0038/1036] Refactor ControlItemText to use bindable flow for unread state --- .../Online/TestSceneChannelControlItem.cs | 4 +-- .../Chat/ChannelControl/ControlItem.cs | 11 ++---- .../Chat/ChannelControl/ControlItemText.cs | 35 +++++++------------ 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 2e5c2cc2cb..9af3b7613b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -117,13 +117,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("Unread Selected", () => { if (selected.Value != null) - channelMap[selected.Value].HasUnread = true; + channelMap[selected.Value].Unread.Value = true; }); AddStep("Read Selected", () => { if (selected.Value != null) - channelMap[selected.Value].HasUnread = false; + channelMap[selected.Value].Unread.Value = false; }); AddStep("Add Mention Selected", () => diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d88a99c484..4b1bbaec82 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -23,15 +23,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl [Cached] public readonly BindableInt Mentions = new BindableInt(); - public bool HasUnread - { - get => text?.HasUnread ?? false; - set - { - if (text != null) - text.HasUnread = value; - } - } + [Cached] + public readonly BindableBool Unread = new BindableBool(); private readonly Channel channel; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 0c91903f6b..3573c72846 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -4,6 +4,7 @@ #nullable enable using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -14,25 +15,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { - private bool hasUnread; - - public bool HasUnread - { - get => hasUnread; - set - { - if (hasUnread == value) - return; - - hasUnread = value; - updateText(); - } - } - private readonly Channel channel; private OsuSpriteText? text; + [Resolved] + private BindableBool unread { get; set; } = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -58,15 +47,17 @@ namespace osu.Game.Overlays.Chat.ChannelControl }; } - private void updateText() + protected override void LoadComplete() { - if (!IsLoaded) - return; + base.LoadComplete(); - if (HasUnread) - text!.Colour = colourProvider.Content1; - else - text!.Colour = colourProvider.Light3; + unread.BindValueChanged(change => + { + if (change.NewValue) + text!.Colour = colourProvider.Content1; + else + text!.Colour = colourProvider.Light3; + }, true); } } } From ec61b88ec27796836ae380c5e845101e1f06f589 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:39:57 +0000 Subject: [PATCH 0039/1036] Adjust ControlItem padding --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 4b1bbaec82..e944bf4c28 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 18, Right = 5 }, + Padding = new MarginPadding { Left = 18, Right = 10 }, Child = new GridContainer { RelativeSizeAxes = Axes.Both, From 73a0373b4ed0d2de982c84b236dafae6c02ee677 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:56:56 +0000 Subject: [PATCH 0040/1036] Code quality fixes --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 6 ++---- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 1 - osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 5 +---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index e944bf4c28..f2bab64371 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -30,8 +30,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl private Box? hoverBox; private Box? selectBox; - private ControlItemText? text; - private ControlItemMention? mention; private ControlItemClose? close; [Resolved] @@ -84,12 +82,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl new[] { createAvatar(), - text = new ControlItemText(channel) + new ControlItemText(channel) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - mention = new ControlItemMention + new ControlItemMention { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 594a52b8a7..e9172d99ba 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -68,6 +68,5 @@ namespace osu.Game.Overlays.Chat.ChannelControl Hide(); }, true); } - } } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 3573c72846..89845dfef8 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -53,10 +53,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl unread.BindValueChanged(change => { - if (change.NewValue) - text!.Colour = colourProvider.Content1; - else - text!.Colour = colourProvider.Light3; + text!.Colour = change.NewValue ? colourProvider.Content1 : colourProvider.Light3; }, true); } } From 6628b7c654789619617475f7114c84b35acde86a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 22:21:18 +0000 Subject: [PATCH 0041/1036] Ensure existing items are expired and cleared in ChannelControlItem test setup --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 9af3b7613b..e496ef60d7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -49,6 +49,11 @@ namespace osu.Game.Tests.Visual.Online { Schedule(() => { + foreach (var item in channelMap.Values) + item.Expire(); + + channelMap.Clear(); + Child = new FillFlowContainer { Direction = FillDirection.Vertical, From 9e476ced63eba9e4ad3e7e2f62d74faf67f079ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:35:46 +0900 Subject: [PATCH 0042/1036] Add `EditorSidebar` component --- .../UserInterface/TestSceneEditorSidebar.cs | 97 +++++++++++++++++++ .../Screens/Edit/Components/EditorSidebar.cs | 52 ++++++++++ .../Edit/Components/EditorSidebarSection.cs | 73 ++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs create mode 100644 osu.Game/Screens/Edit/Components/EditorSidebar.cs create mode 100644 osu.Game/Screens/Edit/Components/EditorSidebarSection.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs new file mode 100644 index 0000000000..7e2b5e0bad --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs @@ -0,0 +1,97 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneEditorSidebar : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Test] + public void TestSidebars() + { + AddStep("Add sidebars", () => + { + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new EditorSidebar + { + Children = new[] + { + new EditorSidebarSection("Section 1") + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(3), + ChildrenEnumerable = Enumerable.Range(0, 10).Select(_ => new Box + { + Colour = Color4.White, + Size = new Vector2(32), + }) + }, + }, + new EditorSidebarSection("Section 2") + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(3), + ChildrenEnumerable = Enumerable.Range(0, 400).Select(_ => new Box + { + Colour = Color4.Gray, + Size = new Vector2(32), + }) + }, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + }, + new EditorSidebar + { + Children = new[] + { + new EditorSidebarSection("Section 1"), + new EditorSidebarSection("Section 2"), + }, + }, + } + } + } + }; + }); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs new file mode 100644 index 0000000000..cd7ef98401 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/EditorSidebar.cs @@ -0,0 +1,52 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Components +{ + /// + /// A sidebar area that can be attached to the left or right edge of the screen. + /// Houses scrolling sectionised content. + /// + internal class EditorSidebar : Container + { + private readonly Box background; + + protected override Container Content { get; } + + public EditorSidebar() + { + Width = 250; + RelativeSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + Padding = new MarginPadding { Left = 20 }, + ScrollbarOverlapsContent = false, + RelativeSizeAxes = Axes.Both, + Child = Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + background.Colour = colourProvider.Background5; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs new file mode 100644 index 0000000000..5c000471a6 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -0,0 +1,73 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Components +{ + public class EditorSidebarSection : Container + { + protected override Container Content { get; } + + public EditorSidebarSection(LocalisableString sectionName) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new SectionHeader(sectionName), + Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + }; + } + + public class SectionHeader : CompositeDrawable + { + private readonly LocalisableString text; + + public SectionHeader(LocalisableString text) + { + this.text = text; + + Margin = new MarginPadding { Vertical = 10, Horizontal = 5 }; + + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Text = text, + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + }, + new Circle + { + Y = 18, + Colour = colourProvider.Highlight1, + Size = new Vector2(28, 2), + } + }; + } + } + } +} \ No newline at end of file From 4ab5d6e3f0b76ad04067fdfb274b188b2c29c0f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:46:57 +0900 Subject: [PATCH 0043/1036] Remove unnecessary `FillFlowContainer` from section --- osu.Game/Screens/Edit/Components/EditorSidebarSection.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index 5c000471a6..319ad82b79 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -27,11 +27,10 @@ namespace osu.Game.Screens.Edit.Components Children = new Drawable[] { new SectionHeader(sectionName), - Content = new FillFlowContainer + Content = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, }, } }; @@ -70,4 +69,4 @@ namespace osu.Game.Screens.Edit.Components } } } -} \ No newline at end of file +} From a0a033520f17b765f91a9a09d346a0cbc115c2b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:48:14 +0900 Subject: [PATCH 0044/1036] Rider no add licence headers --- osu.Game/Screens/Edit/Components/EditorSidebar.cs | 3 +++ osu.Game/Screens/Edit/Components/EditorSidebarSection.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs index cd7ef98401..4edcef41b1 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebar.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebar.cs @@ -1,3 +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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index 319ad82b79..d403694bde 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -1,3 +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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; From 7621e779fa8667110e1b477c5fba3481627ea7f1 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:19:58 +0000 Subject: [PATCH 0045/1036] Move `ControlItem` Action assignments into BDL --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index f2bab64371..d886e8b45a 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -98,12 +98,15 @@ namespace osu.Game.Overlays.Chat.ChannelControl Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Right = 3 }, + Action = () => OnRequestLeave?.Invoke(channel), } } }, }, }, }; + + Action = () => OnRequestSelect?.Invoke(channel); } protected override void LoadComplete() @@ -117,9 +120,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl else selectBox?.Hide(); }, true); - - Action = () => OnRequestSelect?.Invoke(channel); - close!.Action = () => OnRequestLeave?.Invoke(channel); } protected override bool OnHover(HoverEvent e) From 481b8fe80bc043b6a70ad1b799fefd94ba58af06 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:22:32 +0000 Subject: [PATCH 0046/1036] Use `Orange1` for `ControlItemMention` background colour --- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index e9172d99ba..5118386977 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = osuColour.YellowLight, + Colour = osuColour.Orange1, }, countText = new OsuSpriteText { From 49b74d78670d85ef7a3b159b43b9504c5ff7156a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:33:36 +0000 Subject: [PATCH 0047/1036] Use `BindTarget` instead of caching for `ControlItem` mentions bindable flow --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- .../Overlays/Chat/ChannelControl/ControlItemMention.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d886e8b45a..d73ebed817 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -20,7 +20,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl public event Action? OnRequestSelect; public event Action? OnRequestLeave; - [Cached] public readonly BindableInt Mentions = new BindableInt(); [Cached] @@ -92,6 +91,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Right = 3 }, + Mentions = { BindTarget = Mentions }, }, close = new ControlItemClose { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 5118386977..beb2713c92 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -16,10 +16,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private OsuSpriteText? countText; + public readonly BindableInt Mentions = new BindableInt(); - [Resolved] - private BindableInt mentions { get; set; } = null!; + private OsuSpriteText? countText; [Resolved] private OsuColour osuColour { get; set; } = null!; @@ -56,7 +55,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - mentions.BindValueChanged(change => + Mentions.BindValueChanged(change => { int mentionCount = change.NewValue; From e38d9eafa053a4ddc0e374cbead1063a3f194d8c Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:37:15 +0000 Subject: [PATCH 0048/1036] Use `BindTarget` instead of caching for `ControlItem` unread flow --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d73ebed817..cc00550965 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -22,7 +22,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl public readonly BindableInt Mentions = new BindableInt(); - [Cached] public readonly BindableBool Unread = new BindableBool(); private readonly Channel channel; @@ -85,6 +84,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Unread = { BindTarget = Unread }, }, new ControlItemMention { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 89845dfef8..490edb5d28 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -15,13 +15,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { + public readonly BindableBool Unread = new BindableBool(); + private readonly Channel channel; private OsuSpriteText? text; - [Resolved] - private BindableBool unread { get; set; } = null!; - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -51,7 +50,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - unread.BindValueChanged(change => + Unread.BindValueChanged(change => { text!.Colour = change.NewValue ? colourProvider.Content1 : colourProvider.Light3; }, true); From ba1642a680d34ffc5c1f26953735965031542f22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 17:19:08 +0900 Subject: [PATCH 0049/1036] Allow section headers to wrap --- .../UserInterface/TestSceneEditorSidebar.cs | 2 +- .../Edit/Components/EditorSidebarSection.cs | 32 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs index 7e2b5e0bad..f2f475e063 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface }) }, }, - new EditorSidebarSection("Section 2") + new EditorSidebarSection("Section with a really long section header") { Child = new FillFlowContainer { diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index d403694bde..3871720562 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; @@ -49,24 +49,32 @@ namespace osu.Game.Screens.Edit.Components Margin = new MarginPadding { Vertical = 10, Horizontal = 5 }; - AutoSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - InternalChildren = new Drawable[] + InternalChild = new FillFlowContainer { - new OsuSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + Children = new Drawable[] { - Text = text, - Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), - }, - new Circle - { - Y = 18, - Colour = colourProvider.Highlight1, - Size = new Vector2(28, 2), + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold)) + { + Text = text, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + new Circle + { + Colour = colourProvider.Highlight1, + Size = new Vector2(28, 2), + } } }; } From 603527d72d09eaf0e727cf075e98fb9cdc78d51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 18:38:01 +0900 Subject: [PATCH 0050/1036] Fix potential crash when highlighting chat messages Test failed locally in `TestPublicChannelMention`. This test seems to specify that the same message may arrive twice with the same ID, so rather than overthinking this one I propose we just use `FirstOrDefault`. ```csharp TearDown : System.AggregateException : One or more errors occurred. (Sequence contains more than one matching element) ----> System.InvalidOperationException : Sequence contains more than one matching element --TearDown at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task) at osu.Framework.Testing.TestScene.checkForErrors() --InvalidOperationException at System.Linq.ThrowHelper.ThrowMoreThanOneMatchException() at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found) at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate) at osu.Game.Overlays.Chat.DrawableChannel.b__14_0() in /Users/dean/Projects/osu/osu.Game/Overlays/Chat/DrawableChannel.cs:line 102 at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() ``` --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 632517aa31..161fe1d5be 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat if (highlightedMessage.Value == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(highlightedMessage.Value)); + var chatLine = chatLines.FirstOrDefault(c => c.Message.Equals(highlightedMessage.Value)); if (chatLine == null) return; From 2452d84e98fab4a975caef4af19fac619143047d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 18:44:30 +0900 Subject: [PATCH 0051/1036] Add missing `Schedule` call to allow individual tests from `TestSceneMessageNotifier` to pass --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 175d2ea36b..2c253650d5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online private int messageIdCounter; [SetUp] - public void Setup() + public void Setup() => Schedule(() => { if (API is DummyAPIAccess daa) { @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online testContainer.ChatOverlay.Show(); }); - } + }); private bool dummyAPIHandleRequest(APIRequest request) { From 99e3161cf0a40e6e6bfc70d201a967df29f2bcf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:00:32 +0900 Subject: [PATCH 0052/1036] Fix `SkinEditor`'s initial target not being a `Screen` --- osu.Game/Skinning/Editor/SkinEditor.cs | 14 +++++++++----- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ef26682c03..19c39d23d5 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -49,16 +48,20 @@ namespace osu.Game.Skinning.Editor private EditorToolboxGroup settingsToolbox; + public SkinEditor() + { + } + public SkinEditor(Drawable targetScreen) { - RelativeSizeAxes = Axes.Both; - UpdateTargetScreen(targetScreen); } [BackgroundDependencyLoader] private void load() { + RelativeSizeAxes = Axes.Both; + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -155,7 +158,7 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(skinChanged); }, true); - SelectedComponents.BindCollectionChanged(selectionChanged); + SelectedComponents.BindCollectionChanged((_, __) => Scheduler.AddOnce(populateSettings), true); } public void UpdateTargetScreen(Drawable targetScreen) @@ -163,6 +166,7 @@ namespace osu.Game.Skinning.Editor this.targetScreen = targetScreen; SelectedComponents.Clear(); + Scheduler.AddOnce(loadBlueprintContainer); void loadBlueprintContainer() @@ -224,7 +228,7 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Add(component); } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void populateSettings() { settingsToolbox.Clear(); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 08cdbf0aa9..1bab499979 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -21,16 +21,18 @@ namespace osu.Game.Skinning.Editor /// public class SkinEditorOverlay : CompositeDrawable, IKeyBindingHandler { - private readonly ScalingContainer target; + private readonly ScalingContainer scalingContainer; [CanBeNull] private SkinEditor skinEditor; public const float VISIBLE_TARGET_SCALE = 0.8f; - public SkinEditorOverlay(ScalingContainer target) + private Screen lastTargetScreen; + + public SkinEditorOverlay(ScalingContainer scalingContainer) { - this.target = target; + this.scalingContainer = scalingContainer; RelativeSizeAxes = Axes.Both; } @@ -77,7 +79,7 @@ namespace osu.Game.Skinning.Editor return; } - var editor = new SkinEditor(target); + var editor = new SkinEditor(); editor.State.BindValueChanged(editorVisibilityChanged); skinEditor = editor; @@ -95,6 +97,8 @@ namespace osu.Game.Skinning.Editor return; AddInternal(editor); + + SetTarget(lastTargetScreen); }); }); } @@ -105,11 +109,11 @@ namespace osu.Game.Skinning.Editor if (visibility.NewValue == Visibility.Visible) { - target.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); } else { - target.SetCustomRect(null); + scalingContainer.SetCustomRect(null); } } @@ -122,6 +126,8 @@ namespace osu.Game.Skinning.Editor /// public void SetTarget(Screen screen) { + lastTargetScreen = screen; + if (skinEditor == null) return; skinEditor.Save(); From 86960c791f783c614ada963bc105f065f42e3cfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:10:30 +0900 Subject: [PATCH 0053/1036] Close overlays and toolbar on entering the skin editor --- osu.Game/OsuGame.cs | 2 +- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ae117d03d2..25bd3d71de 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1184,7 +1184,7 @@ namespace osu.Game BackButton.Hide(); } - skinEditor.SetTarget((Screen)newScreen); + skinEditor.SetTarget((OsuScreen)newScreen); } private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 1bab499979..2fdb16abfc 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -3,15 +3,16 @@ using System.Diagnostics; using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; +using osu.Game.Screens; namespace osu.Game.Skinning.Editor { @@ -28,7 +29,10 @@ namespace osu.Game.Skinning.Editor public const float VISIBLE_TARGET_SCALE = 0.8f; - private Screen lastTargetScreen; + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + private OsuScreen lastTargetScreen; public SkinEditorOverlay(ScalingContainer scalingContainer) { @@ -105,15 +109,23 @@ namespace osu.Game.Skinning.Editor private void editorVisibilityChanged(ValueChangedEvent visibility) { + Debug.Assert(skinEditor != null); + const float toolbar_padding_requirement = 0.18f; if (visibility.NewValue == Visibility.Visible) { scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + + game?.Toolbar.Hide(); + game?.CloseAllOverlays(); } else { scalingContainer.SetCustomRect(null); + + if (lastTargetScreen?.HideOverlaysOnEnter != true) + game?.Toolbar.Show(); } } @@ -124,7 +136,7 @@ namespace osu.Game.Skinning.Editor /// /// Set a new target screen which will be used to find skinnable components. /// - public void SetTarget(Screen screen) + public void SetTarget(OsuScreen screen) { lastTargetScreen = screen; @@ -136,7 +148,7 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(setTarget, screen); } - private void setTarget(Screen target) + private void setTarget(OsuScreen target) { Debug.Assert(skinEditor != null); From c807ad7e4ee131563ed45079bfde1e00d4226ae3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 19:11:17 +0900 Subject: [PATCH 0054/1036] Ensure toolbar is hidden even when the active screen is changed while the editor is open --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 2fdb16abfc..780e3d5b67 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -84,7 +83,7 @@ namespace osu.Game.Skinning.Editor } var editor = new SkinEditor(); - editor.State.BindValueChanged(editorVisibilityChanged); + editor.State.BindValueChanged(visibility => updateComponentVisibility()); skinEditor = editor; @@ -107,13 +106,13 @@ namespace osu.Game.Skinning.Editor }); } - private void editorVisibilityChanged(ValueChangedEvent visibility) + private void updateComponentVisibility() { Debug.Assert(skinEditor != null); const float toolbar_padding_requirement = 0.18f; - if (visibility.NewValue == Visibility.Visible) + if (skinEditor.State.Value == Visibility.Visible) { scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); @@ -144,6 +143,9 @@ namespace osu.Game.Skinning.Editor skinEditor.Save(); + // ensure the toolbar is re-hidden even if a new screen decides to try and show it. + updateComponentVisibility(); + // AddOnce with parameter will ensure the newest target is loaded if there is any overlap. Scheduler.AddOnce(setTarget, screen); } From d062810ff2b4c04019f2523faa58e2d3cecca758 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:30:46 +0900 Subject: [PATCH 0055/1036] Add basic scene selector --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 +- osu.Game/Skinning/Editor/SkinEditor.cs | 53 +++++++++++++++++++ osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index a0602e21b9..9012492028 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("reload skin editor", () => { skinEditor?.Expire(); - Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE); + Player.ScaleTo(0.8f); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); }); } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 19c39d23d5..37900d9920 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -10,14 +10,20 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select; +using osuTK; namespace osu.Game.Skinning.Editor { @@ -42,6 +48,12 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + private bool hasBegunMutating; private Container content; @@ -105,6 +117,47 @@ namespace osu.Game.Skinning.Editor }, }, }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + Y = 45, + Height = 30, + Name = "Scene library", + Spacing = new Vector2(10), + Padding = new MarginPadding(10), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new PurpleTriangleButton + { + Text = "Song Select", + Width = 100, + Height = 30, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new PurpleTriangleButton + { + Text = "Gameplay", + Width = 100, + Height = 30, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player) }) + }, + } + }, new GridContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 780e3d5b67..9fc233d3e3 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -114,7 +114,7 @@ namespace osu.Game.Skinning.Editor if (skinEditor.State.Value == Visibility.Visible) { - scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.2f, 0.8f - toolbar_padding_requirement, 0.7f), true); game?.Toolbar.Hide(); game?.CloseAllOverlays(); From 8d85723a62f1e0ad3f0d6b9e2e2480ce877b0b72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Mar 2022 19:23:57 +0900 Subject: [PATCH 0056/1036] Split out `SceneLibrary` into its own component --- osu.Game/Skinning/Editor/SkinEditor.cs | 126 ++++++++++++++++--------- 1 file changed, 82 insertions(+), 44 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 37900d9920..9d5d496837 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Screens; @@ -24,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -48,12 +51,6 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } - [Resolved] - private IBindable ruleset { get; set; } - - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } - private bool hasBegunMutating; private Container content; @@ -117,47 +114,12 @@ namespace osu.Game.Skinning.Editor }, }, }, - new FillFlowContainer + new SceneLibrary { RelativeSizeAxes = Axes.X, Y = 45, - Height = 30, - Name = "Scene library", - Spacing = new Vector2(10), - Padding = new MarginPadding(10), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new PurpleTriangleButton - { - Text = "Song Select", - Width = 100, - Height = 30, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is SongSelect) - return; - - screen.Push(new PlaySongSelect()); - }, new[] { typeof(SongSelect) }) - }, - new PurpleTriangleButton - { - Text = "Gameplay", - Width = 100, - Height = 30, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is Player) - return; - - var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); - if (replayGeneratingMod != null) - screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); - }, new[] { typeof(Player) }) - }, - } }, + new GridContainer { RelativeSizeAxes = Axes.Both, @@ -226,7 +188,10 @@ namespace osu.Game.Skinning.Editor { content.Children = new Drawable[] { - new SkinBlueprintContainer(targetScreen), + new SkinBlueprintContainer(targetScreen) + { + Margin = new MarginPadding { Top = 100 }, + } }; } } @@ -345,5 +310,78 @@ namespace osu.Game.Skinning.Editor foreach (var item in items) availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); } + + private class SceneLibrary : CompositeDrawable + { + public const float HEIGHT = 30; + private const float padding = 10; + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.X, + Height = HEIGHT + padding * 2, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) + }, + new FillFlowContainer + { + Name = "Scene library", + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Spacing = new Vector2(padding), + Padding = new MarginPadding(padding), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new PurpleTriangleButton + { + Text = "Song Select", + Width = 100, + Height = HEIGHT, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new PurpleTriangleButton + { + Text = "Gameplay", + Width = 100, + Height = HEIGHT, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player), typeof(SongSelect) }) + }, + } + }, + } + } + }; + } + } } } From c6aa32a003925fcde04c258409e7685016e9b95e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Mar 2022 23:26:27 +0900 Subject: [PATCH 0057/1036] Add basic song select setup for skinnability --- osu.Game/Screens/Select/SongSelect.cs | 5 +++++ osu.Game/Skinning/DefaultSkin.cs | 8 ++++++++ osu.Game/Skinning/SkinnableTarget.cs | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f5b11448f8..2d1a2bce4e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -37,6 +37,7 @@ using osu.Game.Graphics.UserInterface; using System.Diagnostics; using osu.Game.Screens.Play; using osu.Game.Database; +using osu.Game.Skinning; namespace osu.Game.Screens.Select { @@ -235,6 +236,10 @@ namespace osu.Game.Screens.Select } } }, + new SkinnableTargetContainer(SkinnableTarget.SongSelect) + { + RelativeSizeAxes = Axes.Both, + }, }); if (ShowFooter) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 951e3f9cc5..7c6d138f4c 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -70,6 +70,14 @@ namespace osu.Game.Skinning case SkinnableTargetComponent target: switch (target.Target) { + case SkinnableTarget.SongSelect: + var songSelectComponents = new SkinnableTargetComponentsContainer(container => + { + // do stuff when we need to. + }); + + return songSelectComponents; + case SkinnableTarget.MainHUDComponents: var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => { diff --git a/osu.Game/Skinning/SkinnableTarget.cs b/osu.Game/Skinning/SkinnableTarget.cs index 7b1eae126c..09de8a5d71 100644 --- a/osu.Game/Skinning/SkinnableTarget.cs +++ b/osu.Game/Skinning/SkinnableTarget.cs @@ -5,6 +5,7 @@ namespace osu.Game.Skinning { public enum SkinnableTarget { - MainHUDComponents + MainHUDComponents, + SongSelect } } From aff6a5a428d6ff232835d844066161b45b0f7f45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:11:47 +0900 Subject: [PATCH 0058/1036] Better align scene selector with menu bar --- osu.Game/Skinning/Editor/SkinEditor.cs | 27 +++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 9d5d496837..921849c0aa 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -26,7 +26,6 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; -using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -71,6 +70,8 @@ namespace osu.Game.Skinning.Editor { RelativeSizeAxes = Axes.Both; + const float menu_height = 40; + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -78,10 +79,10 @@ namespace osu.Game.Skinning.Editor { new Container { - Name = "Top bar", + Name = "Menu container", RelativeSizeAxes = Axes.X, Depth = float.MinValue, - Height = 40, + Height = menu_height, Children = new Drawable[] { new EditorMenuBar @@ -117,7 +118,7 @@ namespace osu.Game.Skinning.Editor new SceneLibrary { RelativeSizeAxes = Axes.X, - Y = 45, + Y = menu_height, }, new GridContainer @@ -322,22 +323,26 @@ namespace osu.Game.Skinning.Editor [Resolved] private IBindable ruleset { get; set; } + public SceneLibrary() + { + Height = HEIGHT + padding * 2; + } + [BackgroundDependencyLoader] private void load() { InternalChildren = new Drawable[] { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("222") + }, new OsuScrollContainer(Direction.Horizontal) { - RelativeSizeAxes = Axes.X, - Height = HEIGHT + padding * 2, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) - }, new FillFlowContainer { Name = "Scene library", From ee3715f5cfe7e10f1f44d4a564ea0977aebc7bbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:33:01 +0900 Subject: [PATCH 0059/1036] Use `OverlayColourProvider` and adjust metrics to roughly match new designs --- osu.Game/Skinning/Editor/SkinEditor.cs | 52 ++++++++++++++++++++------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 921849c0aa..b891d63da3 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -18,11 +18,12 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; @@ -50,6 +51,9 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private bool hasBegunMutating; private Container content; @@ -314,7 +318,8 @@ namespace osu.Game.Skinning.Editor private class SceneLibrary : CompositeDrawable { - public const float HEIGHT = 30; + public const float BUTTON_HEIGHT = 40; + private const float padding = 10; [Resolved(canBeNull: true)] @@ -325,18 +330,18 @@ namespace osu.Game.Skinning.Editor public SceneLibrary() { - Height = HEIGHT + padding * 2; + Height = BUTTON_HEIGHT + padding * 2; } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider overlayColourProvider) { InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("222") + Colour = overlayColourProvider.Background5, }, new OsuScrollContainer(Direction.Horizontal) { @@ -353,11 +358,18 @@ namespace osu.Game.Skinning.Editor Direction = FillDirection.Horizontal, Children = new Drawable[] { - new PurpleTriangleButton + new OsuSpriteText + { + Text = "Scene library", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + }, + new SceneButton { Text = "Song Select", - Width = 100, - Height = HEIGHT, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Action = () => game?.PerformFromScreen(screen => { if (screen is SongSelect) @@ -366,11 +378,11 @@ namespace osu.Game.Skinning.Editor screen.Push(new PlaySongSelect()); }, new[] { typeof(SongSelect) }) }, - new PurpleTriangleButton + new SceneButton { Text = "Gameplay", - Width = 100, - Height = HEIGHT, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Action = () => game?.PerformFromScreen(screen => { if (screen is Player) @@ -387,6 +399,22 @@ namespace osu.Game.Skinning.Editor } }; } + + private class SceneButton : OsuButton + { + public SceneButton() + { + Width = 100; + Height = BUTTON_HEIGHT; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + { + BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; + Content.CornerRadius = 5; + } + } } } } From b08d4bb8eb12c6d8f7337aa1445b821203cd1c95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:34:04 +0900 Subject: [PATCH 0060/1036] Move `SceneLibrary` implementation to its own file --- osu.Game/Skinning/Editor/SkinEditor.cs | 111 +--------------- .../Skinning/Editor/SkinEditorSceneLibrary.cs | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+), 110 deletions(-) create mode 100644 osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b891d63da3..acae09ee71 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -4,29 +4,21 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.Play; -using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Skinning.Editor { @@ -119,7 +111,7 @@ namespace osu.Game.Skinning.Editor }, }, }, - new SceneLibrary + new SkinEditorSceneLibrary { RelativeSizeAxes = Axes.X, Y = menu_height, @@ -315,106 +307,5 @@ namespace osu.Game.Skinning.Editor foreach (var item in items) availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); } - - private class SceneLibrary : CompositeDrawable - { - public const float BUTTON_HEIGHT = 40; - - private const float padding = 10; - - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } - - [Resolved] - private IBindable ruleset { get; set; } - - public SceneLibrary() - { - Height = BUTTON_HEIGHT + padding * 2; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider overlayColourProvider) - { - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = overlayColourProvider.Background5, - }, - new OsuScrollContainer(Direction.Horizontal) - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new FillFlowContainer - { - Name = "Scene library", - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Spacing = new Vector2(padding), - Padding = new MarginPadding(padding), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Scene library", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding(10), - }, - new SceneButton - { - Text = "Song Select", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is SongSelect) - return; - - screen.Push(new PlaySongSelect()); - }, new[] { typeof(SongSelect) }) - }, - new SceneButton - { - Text = "Gameplay", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is Player) - return; - - var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); - if (replayGeneratingMod != null) - screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); - }, new[] { typeof(Player), typeof(SongSelect) }) - }, - } - }, - } - } - }; - } - - private class SceneButton : OsuButton - { - public SceneButton() - { - Width = 100; - Height = BUTTON_HEIGHT; - } - - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) - { - BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; - Content.CornerRadius = 5; - } - } - } } } diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs new file mode 100644 index 0000000000..5da6147e4c --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -0,0 +1,123 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Skinning.Editor +{ + public class SkinEditorSceneLibrary : CompositeDrawable + { + public const float BUTTON_HEIGHT = 40; + + private const float padding = 10; + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + public SkinEditorSceneLibrary() + { + Height = BUTTON_HEIGHT + padding * 2; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider overlayColourProvider) + { + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColourProvider.Background6, + }, + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Name = "Scene library", + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Spacing = new Vector2(padding), + Padding = new MarginPadding(padding), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Scene library", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + }, + new SceneButton + { + Text = "Song Select", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new SceneButton + { + Text = "Gameplay", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player), typeof(SongSelect) }) + }, + } + }, + } + } + }; + } + + private class SceneButton : OsuButton + { + public SceneButton() + { + Width = 100; + Height = BUTTON_HEIGHT; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + { + BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; + Content.CornerRadius = 5; + } + } + } +} From fdb411c0f3dcd9bf7ea7e5ec4a13028dac551160 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:19:20 +0900 Subject: [PATCH 0061/1036] Update skin editor toolbox design to suck less --- .../Skinning/Editor/SkinComponentToolbox.cs | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 2781f1958f..e5fe13b64d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -6,16 +6,15 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -23,7 +22,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osuTK; -using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -33,8 +31,6 @@ namespace osu.Game.Skinning.Editor public Action RequestPlacement; - private const float component_display_scale = 0.8f; - [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) { @@ -62,7 +58,7 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(20) + Spacing = new Vector2(2) }; var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() @@ -120,39 +116,36 @@ namespace osu.Game.Skinning.Editor Enabled.Value = true; RelativeSizeAxes = Axes.X; - Height = 70; + Height = 60; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours) { - BackgroundColour = colours.Gray3; - Content.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 2, - Offset = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.5f) - }; + BackgroundColour = colourProvider.Background3; AddRange(new Drawable[] { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10) { Bottom = 20 }, + Masking = true, + Child = innerContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = component + }, + }, new OsuSpriteText { Text = component.GetType().Name, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding(5), }, - innerContainer = new Container - { - Y = 10, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(component_display_scale), - Masking = true, - Child = component - } }); // adjust provided component to fit / display in a known state. @@ -160,14 +153,17 @@ namespace osu.Game.Skinning.Editor component.Origin = Anchor.Centre; } - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - if (component.RelativeSizeAxes != Axes.None) + if (component.DrawSize != Vector2.Zero) { - innerContainer.AutoSizeAxes = Axes.None; - innerContainer.Height = 100; + float bestScale = Math.Min( + innerContainer.DrawWidth / component.DrawWidth, + innerContainer.DrawHeight / component.DrawHeight); + + innerContainer.Scale = new Vector2(bestScale); } } From e4a6b7ae9139ec2b8600accc717fcad1e525a3cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:33:06 +0900 Subject: [PATCH 0062/1036] Expand toolbox component items on hover --- .../Skinning/Editor/SkinComponentToolbox.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index e5fe13b64d..3980163a9d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -109,6 +109,9 @@ namespace osu.Game.Skinning.Editor private Container innerContainer; + private const float contracted_size = 60; + private const float expanded_size = 120; + public ToolboxComponentButton(Drawable component) { this.component = component; @@ -116,7 +119,19 @@ namespace osu.Game.Skinning.Editor Enabled.Value = true; RelativeSizeAxes = Axes.X; - Height = 60; + Height = contracted_size; + } + + protected override bool OnHover(HoverEvent e) + { + this.Delay(300).ResizeHeightTo(expanded_size, 500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + this.ResizeHeightTo(contracted_size, 500, Easing.OutQuint); } [BackgroundDependencyLoader] From 59cb1ac126bed54c93ccc600cc134696d7d82482 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:35:25 +0900 Subject: [PATCH 0063/1036] Order components by name for now --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 3980163a9d..98fdc7c93f 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -64,6 +64,7 @@ namespace osu.Game.Skinning.Editor var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() .Where(t => !t.IsInterface) .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) + .OrderBy(t => t.Name) .ToArray(); foreach (var type in skinnableTypes) From 4525ed645c4c844c21774eeb6b2ced2e0028c3e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:36:04 +0900 Subject: [PATCH 0064/1036] Update skin editor to use `EditorSidebar` --- .../TestSceneSkinEditorComponentsList.cs | 12 ++++++--- .../Skinning/Editor/SkinComponentToolbox.cs | 12 +++------ osu.Game/Skinning/Editor/SkinEditor.cs | 27 +++++++++++++------ .../Skinning/Editor/SkinSettingsToolbox.cs | 20 ++++++++------ 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index 58c89411c0..5385a9983b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning.Editor; @@ -11,13 +13,17 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditorComponentsList : SkinnableTestScene { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + [Test] public void TestToggleEditor() { - AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(300) + AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 0.6f, })); } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 2781f1958f..05da229c3e 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -18,19 +18,17 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Components; using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning.Editor { - public class SkinComponentToolbox : ScrollingToolboxGroup + public class SkinComponentToolbox : EditorSidebarSection { - public const float WIDTH = 200; - public Action RequestPlacement; private const float component_display_scale = 0.8f; @@ -45,11 +43,9 @@ namespace osu.Game.Skinning.Editor [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - public SkinComponentToolbox(float height) - : base("Components", height) + public SkinComponentToolbox() + : base("Components") { - RelativeSizeAxes = Axes.None; - Width = WIDTH; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index acae09ee71..381f7a1cc3 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor @@ -130,21 +131,31 @@ namespace osu.Game.Skinning.Editor { new Drawable[] { - new SkinComponentToolbox(600) + new EditorSidebar { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RequestPlacement = placeComponent + Children = new[] + { + new SkinComponentToolbox + { + RequestPlacement = placeComponent + }, + } }, content = new Container { RelativeSizeAxes = Axes.Both, }, - settingsToolbox = new SkinSettingsToolbox + new EditorSidebar { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } + Children = new[] + { + settingsToolbox = new SkinSettingsToolbox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + } + }, } } } diff --git a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs index c0ef8e7316..fc06d3647a 100644 --- a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs @@ -2,22 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Edit; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Edit.Components; using osuTK; namespace osu.Game.Skinning.Editor { - internal class SkinSettingsToolbox : ScrollingToolboxGroup + internal class SkinSettingsToolbox : EditorSidebarSection { - public const float WIDTH = 200; + protected override Container Content { get; } public SkinSettingsToolbox() - : base("Settings", 600) + : base("Settings") { - RelativeSizeAxes = Axes.None; - Width = WIDTH; - - FillFlow.Spacing = new Vector2(10); + base.Content.Add(Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + }); } } } From 54e351efe9e6dfe53fe13034108a454ff480ab03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:57:39 +0900 Subject: [PATCH 0065/1036] Convert top level skin editor layout to use grid container Fix `SkinEditor`'s initial target not being a `Screen` --- osu.Game/Skinning/Editor/SkinEditor.cs | 180 +++++++++++++------------ 1 file changed, 95 insertions(+), 85 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 381f7a1cc3..f7e5aeecdf 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -51,7 +50,7 @@ namespace osu.Game.Skinning.Editor private Container content; - private EditorToolboxGroup settingsToolbox; + private EditorSidebarSection settingsToolbox; public SkinEditor() { @@ -72,92 +71,111 @@ namespace osu.Game.Skinning.Editor InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = new GridContainer { - new Container + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - Name = "Menu container", - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = menu_height, - Children = new Drawable[] - { - new EditorMenuBar - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] - { - new MenuItem("File") - { - Items = new[] - { - new EditorMenuItem("Save", MenuItemType.Standard, Save), - new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit", MenuItemType.Standard, Hide), - }, - }, - } - }, - headerText = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopRight, - Padding = new MarginPadding(5), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, - }, - }, - new SkinEditorSceneLibrary - { - RelativeSizeAxes = Axes.X, - Y = menu_height, + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), }, - new GridContainer + Content = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] + new Container { - new EditorSidebar + Name = "Menu container", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = menu_height, + Children = new Drawable[] { - Children = new[] + new EditorMenuBar { - new SkinComponentToolbox + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] { - RequestPlacement = placeComponent + new MenuItem("File") + { + Items = new[] + { + new EditorMenuItem("Save", MenuItemType.Standard, Save), + new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit", MenuItemType.Standard, Hide), + }, + }, + } + }, + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, + }, + }, + }, + new Drawable[] + { + new SkinEditorSceneLibrary + { + RelativeSizeAxes = Axes.X, + }, + }, + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new EditorSidebar + { + Children = new[] + { + new SkinComponentToolbox + { + RequestPlacement = placeComponent + }, + } + }, + content = new Container + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, + new EditorSidebar + { + Children = new[] + { + settingsToolbox = new SkinSettingsToolbox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + } }, } - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - new EditorSidebar - { - Children = new[] - { - settingsToolbox = new SkinSettingsToolbox - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } - } - }, + } } - } + }, } } }; @@ -191,17 +209,9 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Clear(); Scheduler.AddOnce(loadBlueprintContainer); + Scheduler.AddOnce(populateSettings); - void loadBlueprintContainer() - { - content.Children = new Drawable[] - { - new SkinBlueprintContainer(targetScreen) - { - Margin = new MarginPadding { Top = 100 }, - } - }; - } + void loadBlueprintContainer() => content.Child = new SkinBlueprintContainer(targetScreen); } private void skinChanged() From 27122c17c91e1b2e0e5cc14a5f13c37d6123ef64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:41:20 +0900 Subject: [PATCH 0066/1036] Show settings for multiple components in a selection --- osu.Game/Skinning/Editor/SkinEditor.cs | 25 ++++--------------- .../Skinning/Editor/SkinSettingsToolbox.cs | 7 ++++-- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f7e5aeecdf..6fe3df6e8a 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Testing; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -50,7 +49,7 @@ namespace osu.Game.Skinning.Editor private Container content; - private EditorSidebarSection settingsToolbox; + private EditorSidebar settingsSidebar; public SkinEditor() { @@ -161,17 +160,7 @@ namespace osu.Game.Skinning.Editor Depth = float.MaxValue, RelativeSizeAxes = Axes.Both, }, - new EditorSidebar - { - Children = new[] - { - settingsToolbox = new SkinSettingsToolbox - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } - } - }, + settingsSidebar = new EditorSidebar(), } } } @@ -266,14 +255,10 @@ namespace osu.Game.Skinning.Editor private void populateSettings() { - settingsToolbox.Clear(); + settingsSidebar.Clear(); - var first = SelectedComponents.OfType().FirstOrDefault(); - - if (first != null) - { - settingsToolbox.Children = first.CreateSettingsControls().ToArray(); - } + foreach (var component in SelectedComponents.OfType()) + settingsSidebar.Add(new SkinSettingsToolbox(component)); } private IEnumerable availableTargets => targetScreen.ChildrenOfType(); diff --git a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs index fc06d3647a..d2823ed0e4 100644 --- a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs @@ -1,8 +1,10 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Screens.Edit.Components; using osuTK; @@ -12,8 +14,8 @@ namespace osu.Game.Skinning.Editor { protected override Container Content { get; } - public SkinSettingsToolbox() - : base("Settings") + public SkinSettingsToolbox(Drawable component) + : base($"Settings ({component.GetType().Name})") { base.Content.Add(Content = new FillFlowContainer { @@ -21,6 +23,7 @@ namespace osu.Game.Skinning.Editor AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(10), + Children = component.CreateSettingsControls().ToArray() }); } } From cc356bcfe4afacb6392e22bdfa665cf4f3249cc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:57:53 +0900 Subject: [PATCH 0067/1036] Show components available for current screen only (using actual live dependencies) --- .../Skinning/Editor/SkinComponentToolbox.cs | 76 ++++++++----------- osu.Game/Skinning/Editor/SkinEditor.cs | 31 ++++---- 2 files changed, 47 insertions(+), 60 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 483e365e78..9c4a369c1d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -2,24 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components; using osuTK; @@ -29,26 +21,19 @@ namespace osu.Game.Skinning.Editor { public Action RequestPlacement; - [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) - { - Combo = { Value = RNG.Next(1, 1000) }, - TotalScore = { Value = RNG.Next(1000, 10000000) } - }; + private readonly CompositeDrawable target; - [Cached(typeof(HealthProcessor))] - private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - - public SkinComponentToolbox() + public SkinComponentToolbox(CompositeDrawable target = null) : base("Components") { + this.target = target; } + private FillFlowContainer fill; + [BackgroundDependencyLoader] private void load() { - FillFlowContainer fill; - Child = fill = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -57,6 +42,13 @@ namespace osu.Game.Skinning.Editor Spacing = new Vector2(2) }; + reloadComponents(); + } + + private void reloadComponents() + { + fill.Clear(); + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() .Where(t => !t.IsInterface) .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) @@ -64,18 +56,10 @@ namespace osu.Game.Skinning.Editor .ToArray(); foreach (var type in skinnableTypes) - { - var component = attemptAddComponent(type); - - if (component != null) - { - component.RequestPlacement = t => RequestPlacement?.Invoke(t); - fill.Add(component); - } - } + attemptAddComponent(type); } - private static ToolboxComponentButton attemptAddComponent(Type type) + private void attemptAddComponent(Type type) { try { @@ -83,14 +67,15 @@ namespace osu.Game.Skinning.Editor Debug.Assert(instance != null); - if (!((ISkinnableDrawable)instance).IsEditable) - return null; + if (!((ISkinnableDrawable)instance).IsEditable) return; - return new ToolboxComponentButton(instance); + fill.Add(new ToolboxComponentButton(instance, target) + { + RequestPlacement = t => RequestPlacement?.Invoke(t) + }); } catch { - return null; } } @@ -101,6 +86,7 @@ namespace osu.Game.Skinning.Editor public override bool PropagateNonPositionalInputSubTree => false; private readonly Drawable component; + private readonly CompositeDrawable dependencySource; public Action RequestPlacement; @@ -109,9 +95,10 @@ namespace osu.Game.Skinning.Editor private const float contracted_size = 60; private const float expanded_size = 120; - public ToolboxComponentButton(Drawable component) + public ToolboxComponentButton(Drawable component, CompositeDrawable dependencySource) { this.component = component; + this.dependencySource = dependencySource; Enabled.Value = true; @@ -143,7 +130,7 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(10) { Bottom = 20 }, Masking = true, - Child = innerContainer = new Container + Child = innerContainer = new DependencyBorrowingContainer(dependencySource) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -186,14 +173,17 @@ namespace osu.Game.Skinning.Editor } } - private class DummyRuleset : Ruleset + public class DependencyBorrowingContainer : Container { - public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); - public override string Description => string.Empty; - public override string ShortName => string.Empty; + private readonly CompositeDrawable donor; + + public DependencyBorrowingContainer(CompositeDrawable donor) + { + this.donor = donor; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + new DependencyContainer(donor?.Dependencies ?? base.CreateChildDependencies(parent)); } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f7e5aeecdf..2a7e3439c0 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -51,6 +51,7 @@ namespace osu.Game.Skinning.Editor private Container content; private EditorSidebarSection settingsToolbox; + private EditorSidebar componentsSidebar; public SkinEditor() { @@ -146,16 +147,7 @@ namespace osu.Game.Skinning.Editor { new Drawable[] { - new EditorSidebar - { - Children = new[] - { - new SkinComponentToolbox - { - RequestPlacement = placeComponent - }, - } - }, + componentsSidebar = new EditorSidebar(), content = new Container { Depth = float.MaxValue, @@ -211,7 +203,15 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(loadBlueprintContainer); Scheduler.AddOnce(populateSettings); - void loadBlueprintContainer() => content.Child = new SkinBlueprintContainer(targetScreen); + void loadBlueprintContainer() + { + content.Child = new SkinBlueprintContainer(targetScreen); + + componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable) + { + RequestPlacement = placeComponent + }; + } } private void skinChanged() @@ -238,12 +238,7 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var target = availableTargets.FirstOrDefault()?.Target; - - if (target == null) - return; - - var targetContainer = getTarget(target.Value); + var targetContainer = getFirstTarget(); if (targetContainer == null) return; @@ -278,6 +273,8 @@ namespace osu.Game.Skinning.Editor private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private ISkinnableTarget getFirstTarget() => availableTargets.FirstOrDefault(); + private ISkinnableTarget getTarget(SkinnableTarget target) { return availableTargets.FirstOrDefault(c => c.Target == target); From 7e5262364525dc4cd0685b3ef8bc4287e8092b74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 20:02:52 +0900 Subject: [PATCH 0068/1036] Add "Reset position" menu item in skin editor --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index bd6d097eb2..46755728a7 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -199,6 +200,12 @@ namespace osu.Game.Skinning.Editor Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray() }; + yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () => + { + foreach (var blueprint in SelectedBlueprints) + ((Drawable)blueprint.Item).Position = Vector2.Zero; + }); + foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; From c2e4779c2ed429a5e54dd405938e80347f1d2bab Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 16 Mar 2022 11:57:11 +0000 Subject: [PATCH 0069/1036] Remove redundant spacing in `ControlItem` test scene --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index e496ef60d7..8289be033a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -91,7 +91,6 @@ namespace osu.Game.Tests.Visual.Online flow = new FillFlowContainer { Direction = FillDirection.Vertical, - Spacing = new Vector2(20), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, From 3f61b0d9688e99221c51522563be6aee7939c5aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 22:23:19 +0900 Subject: [PATCH 0070/1036] Add missing `OverlayColourProvider` to `SkinEditor` test --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 9012492028..e74345aae9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning.Editor; @@ -16,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool Autoplay => true; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + [SetUpSteps] public override void SetUpSteps() { From 03d3969b38ebd37e03e8b09c4f6eed5218f4766d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:46:02 +0100 Subject: [PATCH 0071/1036] Remove unnecessary `OsuColour` cache One is already cached at `OsuGameBase` level. --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 8289be033a..3465e7dfec 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -8,7 +8,6 @@ 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.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -24,9 +23,6 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - [Cached] - private readonly OsuColour osuColour = new OsuColour(); - [Cached] private readonly Bindable selected = new Bindable(); From a8cefca685f9ec5faed81112a70f894c838f7f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:49:04 +0100 Subject: [PATCH 0072/1036] Simplify fade-out transform --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 3465e7dfec..e66092bf27 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Online private void leaveChannel(Channel channel) { leaveText.Text = $"OnRequestLeave: {channel.Name}"; - leaveText.FadeIn().Then().FadeOut(1000, Easing.InQuint); + leaveText.FadeOutFromOne(1000, Easing.InQuint); } private static Channel createPublicChannel(string name) => From b21fa78cbfa57b749138fdcd36a256ea727bc610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:55:36 +0100 Subject: [PATCH 0073/1036] Move dependencies out of fields to BDL args where possible --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 5 +---- .../Overlays/Chat/ChannelControl/ControlItemMention.cs | 8 +------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index cc00550965..c27d5fdf5d 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -33,16 +33,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl [Resolved] private Bindable selectedChannel { get; set; } = null!; - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - public ControlItem(Channel channel) { this.channel = channel; } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { Height = 30; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index beb2713c92..34e1e33c87 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -20,14 +20,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; - [Resolved] - private OsuColour osuColour { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - [BackgroundDependencyLoader] - private void load() + private void load(OsuColour osuColour, OverlayColourProvider colourProvider) { Masking = true; Size = new Vector2(20, 12); From 624f9fc774732ae8c7ee795968414d3f7f97926b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 22:41:28 +0100 Subject: [PATCH 0074/1036] Implement mod settings area component --- .../UserInterface/TestSceneModSettingsArea.cs | 40 ++++ osu.Game/Overlays/Mods/ModSettingsArea.cs | 176 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs create mode 100644 osu.Game/Overlays/Mods/ModSettingsArea.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs new file mode 100644 index 0000000000..ddc1c8c128 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs @@ -0,0 +1,40 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSettingsArea : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestModToggleArea() + { + ModSettingsArea modSettingsArea = null; + + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = modSettingsArea = new ModSettingsArea() + }); + AddStep("set DT", () => modSettingsArea.SelectedMods.Value = new[] { new OsuModDoubleTime() }); + AddStep("set DA", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); + AddStep("set FL+WU+DA+AD", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }); + AddStep("set empty", () => modSettingsArea.SelectedMods.Value = Array.Empty()); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSettingsArea.cs b/osu.Game/Overlays/Mods/ModSettingsArea.cs new file mode 100644 index 0000000000..e0a30f60c2 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSettingsArea.cs @@ -0,0 +1,176 @@ +// 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.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +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.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModSettingsArea : CompositeDrawable + { + public Bindable> SelectedMods { get; } = new Bindable>(); + + private readonly Box background; + private readonly FillFlowContainer modSettingsFlow; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public ModSettingsArea() + { + RelativeSizeAxes = Axes.X; + Height = 250; + + Anchor = Anchor.BottomRight; + Origin = Anchor.BottomRight; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 2, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.Both, + ScrollbarOverlapsContent = false, + Child = modSettingsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Vertical = 7, Horizontal = 70 }, + Spacing = new Vector2(7), + Direction = FillDirection.Horizontal + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colourProvider.Dark3; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + SelectedMods.BindValueChanged(_ => updateMods()); + } + + private void updateMods() + { + modSettingsFlow.Clear(); + + foreach (var mod in SelectedMods.Value.OrderBy(mod => mod.Type).ThenBy(mod => mod.Acronym)) + { + var settings = mod.CreateSettingsControls().ToList(); + + if (settings.Count > 0) + { + if (modSettingsFlow.Any()) + { + modSettingsFlow.Add(new Box + { + RelativeSizeAxes = Axes.Y, + Width = 2, + Colour = colourProvider.Dark4, + }); + } + + modSettingsFlow.Add(new ModSettingsColumn(mod, settings)); + } + } + } + + protected override bool OnMouseDown(MouseDownEvent e) => true; + protected override bool OnHover(HoverEvent e) => true; + + private class ModSettingsColumn : CompositeDrawable + { + public ModSettingsColumn(Mod mod, IEnumerable settingsControls) + { + Width = 250; + RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding { Bottom = 7 }; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + new ModSwitchTiny(mod) + { + Active = { Value = true }, + Scale = new Vector2(0.6f), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft + }, + new OsuSpriteText + { + Text = mod.Name, + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Bottom = 2 } + } + } + } + }, + new[] { Empty() }, + new Drawable[] + { + new OsuScrollContainer(Direction.Vertical) + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 7 }, + ChildrenEnumerable = settingsControls, + Spacing = new Vector2(0, 7) + } + } + } + } + }; + } + } + } +} From 1a04260807bfaf54675e753b4a0b7561d7349fad Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 05:51:12 +0300 Subject: [PATCH 0075/1036] Update mod instantiaton utility method to no longer check for validity --- osu.Game/Utils/ModUtils.cs | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d5ea74c404..bccb706842 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -153,31 +153,17 @@ namespace osu.Game.Utils } /// - /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. + /// Returns an instantiated list of all proposed mods on a given ruleset. /// - /// The ruleset to verify mods against. + /// The ruleset to instantiate mods. /// The proposed mods. - /// Mods instantiated from which were valid for the given . - /// Whether all were valid for the given . - public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List valid) + /// Mods instantiated from on the given . + public static void InstantiateModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List mods) { - valid = new List(); - bool proposedWereValid = true; + mods = new List(); foreach (var apiMod in proposedMods) - { - try - { - // will throw if invalid - valid.Add(apiMod.ToMod(ruleset)); - } - catch - { - proposedWereValid = false; - } - } - - return proposedWereValid; + mods.Add(apiMod.ToMod(ruleset)); } } } From 83189d1f07dc21af413f74f48e38c52082d6d005 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 06:24:55 +0300 Subject: [PATCH 0076/1036] Revert "Update mod instantiaton utility method to no longer check for validity" This reverts commit 1a04260807bfaf54675e753b4a0b7561d7349fad. --- osu.Game/Utils/ModUtils.cs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index bccb706842..d5ea74c404 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -153,17 +153,31 @@ namespace osu.Game.Utils } /// - /// Returns an instantiated list of all proposed mods on a given ruleset. + /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. /// - /// The ruleset to instantiate mods. + /// The ruleset to verify mods against. /// The proposed mods. - /// Mods instantiated from on the given . - public static void InstantiateModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List mods) + /// Mods instantiated from which were valid for the given . + /// Whether all were valid for the given . + public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List valid) { - mods = new List(); + valid = new List(); + bool proposedWereValid = true; foreach (var apiMod in proposedMods) - mods.Add(apiMod.ToMod(ruleset)); + { + try + { + // will throw if invalid + valid.Add(apiMod.ToMod(ruleset)); + } + catch + { + proposedWereValid = false; + } + } + + return proposedWereValid; } } } From e0a06bf5d931ceffb4ff0a42d4269483896d99df Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 06:28:10 +0300 Subject: [PATCH 0077/1036] Update mod instantiation utility method inline with `APIMod.ToMod` changes --- osu.Game/Utils/ModUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d5ea74c404..ff8e04cc58 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -166,15 +166,15 @@ namespace osu.Game.Utils foreach (var apiMod in proposedMods) { - try - { - // will throw if invalid - valid.Add(apiMod.ToMod(ruleset)); - } - catch + var mod = apiMod.ToMod(ruleset); + + if (mod is UnknownMod) { proposedWereValid = false; + continue; } + + valid.Add(mod); } return proposedWereValid; From 1eac0f41bf4e624e32d125de2635561a8ff01e14 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 13:44:54 +0900 Subject: [PATCH 0078/1036] Remove unused using --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 46755728a7..d7fb5c0498 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; From 46e66e66e477507c3c7564930b2c615ed81f47da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 08:19:38 +0300 Subject: [PATCH 0079/1036] Make opening chat overlay opt-in to add coverage for unloaded chat overlays Causes `TestHighlightWhileChatNeverOpen` to fail as expected. --- .../Visual/Online/TestSceneChatOverlay.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 00ff6a9576..80a6698761 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -122,6 +122,8 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestHideOverlay() { + AddStep("Open chat overlay", () => chatOverlay.Show()); + AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); @@ -134,6 +136,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelSelection() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); AddStep("Setup get message response", () => onGetMessages = channel => { @@ -169,6 +172,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestSearchInSelector() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Search for 'no. 2'", () => chatOverlay.ChildrenOfType().First().Text = "no. 2"); AddUntilStep("Only channel 2 visible", () => { @@ -180,6 +184,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelShortcutKeys() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel))); AddStep("Close channel selector", () => InputManager.Key(Key.Escape)); AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); @@ -199,6 +204,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestCloseChannelBehaviour() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddUntilStep("Join until dropdown has channels", () => { if (visibleChannels.Count() < joinedChannels.Count()) @@ -269,6 +275,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelCloseButton() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -289,6 +296,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestCloseTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -314,6 +322,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestNewTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -330,6 +339,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestRestoreTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 3 channels", () => { channelManager.JoinChannel(channel1); @@ -375,6 +385,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChatCommand() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -398,6 +409,8 @@ namespace osu.Game.Tests.Visual.Online { Channel multiplayerChannel = null; + AddStep("open chat overlay", () => chatOverlay.Show()); + AddStep("join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser()) { Name = "#mp_1", @@ -417,6 +430,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -443,6 +457,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -471,6 +486,8 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -496,14 +513,11 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestHighlightWhileChatHidden() + public void TestHighlightWhileChatNeverOpen() { Message message = null; - AddStep("hide chat", () => chatOverlay.Hide()); - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); AddStep("Send message in channel 1", () => { @@ -520,7 +534,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message and show chat", () => + AddStep("Highlight message and open chat", () => { chatOverlay.HighlightMessage(message, channel1); chatOverlay.Show(); @@ -571,8 +585,6 @@ namespace osu.Game.Tests.Visual.Online ChannelManager, ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }, }; - - ChatOverlay.Show(); } } From 7aae9bbd1b33943fab4301934db6d3d20322660e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 08:31:38 +0300 Subject: [PATCH 0080/1036] Improve channel bindable logic in `ChatOverlay` to avoid potential nullrefs --- osu.Game/Overlays/ChatOverlay.cs | 38 ++++++++++++-------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 3764ac42fc..3d39c7ce3a 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -73,6 +73,10 @@ namespace osu.Game.Overlays private Container channelSelectionContainer; protected ChannelSelectionOverlay ChannelSelectionOverlay; + private readonly IBindableList availableChannels = new BindableList(); + private readonly IBindableList joinedChannels = new BindableList(); + private readonly Bindable currentChannel = new Bindable(); + public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); @@ -198,9 +202,13 @@ namespace osu.Game.Overlays }, }; + availableChannels.BindTo(channelManager.AvailableChannels); + joinedChannels.BindTo(channelManager.JoinedChannels); + currentChannel.BindTo(channelManager.CurrentChannel); + textBox.OnCommit += postMessage; - ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; + ChannelTabControl.Current.ValueChanged += current => currentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => { @@ -238,18 +246,12 @@ namespace osu.Game.Overlays Schedule(() => { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. - channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true); - - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; - availableChannelsChanged(null, null); - - currentChannel = channelManager.CurrentChannel.GetBoundCopy(); + joinedChannels.BindCollectionChanged(joinedChannelsChanged, true); + availableChannels.BindCollectionChanged(availableChannelsChanged, true); currentChannel.BindValueChanged(currentChannelChanged, true); }); } - private Bindable currentChannel; - private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -318,7 +320,7 @@ namespace osu.Game.Overlays if (!channel.Joined.Value) channel = channelManager.JoinChannel(channel); - channelManager.CurrentChannel.Value = channel; + currentChannel.Value = channel; } channel.HighlightedMessage.Value = message; @@ -407,7 +409,7 @@ namespace osu.Game.Overlays return true; case PlatformAction.DocumentClose: - channelManager.LeaveChannel(channelManager.CurrentChannel.Value); + channelManager.LeaveChannel(currentChannel.Value); return true; } @@ -487,19 +489,7 @@ namespace osu.Game.Overlays private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { - ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (channelManager != null) - { - channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged; - } + ChannelSelectionOverlay.UpdateAvailableChannels(availableChannels); } private void postMessage(TextBox textBox, bool newText) From ac739c9dae7a6ab6ca09eac1e9a9b93eeeac5ce4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 22:55:47 +0900 Subject: [PATCH 0081/1036] Change `PerformancePointsCounter` resolution requirements to be required All other similar UI components have required dependencies, so this is mainly to bring things in line with expectations. I am using this fact in the skin editor to only show components which can be used in the current editor context (by `try-catch`ing their `Activator.CreateInstance`). --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 5 +++++ osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 4 ++++ .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 4 ++++ .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 4 ++++ osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 6 ++---- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index c5ea9e6204..c70313ee8a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -18,9 +18,11 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { @@ -37,6 +39,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + protected override bool HasCustomSteps => true; [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 505f73159f..d6a4d9bf79 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -30,6 +31,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 8f33f6fac5..5dc4bdb041 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning.Editor; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -22,6 +23,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + [SetUpSteps] public void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index cdf349ff7f..da443a6c92 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -29,6 +30,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index c0d0ea0721..7a1f724cfb 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -42,12 +42,10 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; - [CanBeNull] - [Resolved(CanBeNull = true)] + [Resolved] private ScoreProcessor scoreProcessor { get; set; } - [Resolved(CanBeNull = true)] - [CanBeNull] + [Resolved] private GameplayState gameplayState { get; set; } [CanBeNull] From fd71aa4a4d321078f0da1194db47f18590052c8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Mar 2022 15:05:51 +0900 Subject: [PATCH 0082/1036] Change `SongProgress` resolution requirements to be required --- osu.Game/Screens/Play/SongProgress.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index b27a9c5f5d..694b5ec628 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -73,9 +73,12 @@ namespace osu.Game.Screens.Play [Resolved(canBeNull: true)] private Player player { get; set; } - [Resolved(canBeNull: true)] + [Resolved] private GameplayClock gameplayClock { get; set; } + [Resolved] + private DrawableRuleset drawableRuleset { get; set; } + private IClock referenceClock; public bool UsesFixedAnchor { get; set; } @@ -113,7 +116,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset) + private void load(OsuColour colours, OsuConfigManager config) { base.LoadComplete(); From 93bc0ac86966bed11fd59ce02df600e4cf717844 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Mar 2022 17:49:35 +0900 Subject: [PATCH 0083/1036] Update download links in README.md for macOS (to add Apple Silicon) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f64240f67a..dba0b2670d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. From 7b8fb341a54e9a5dab83bf363aec810cc466782e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 17:59:26 +0900 Subject: [PATCH 0084/1036] Fix not handling IconButtons --- osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index b7aa8af4aa..2deb8686cc 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -127,9 +127,9 @@ namespace osu.Game.Tests.Visual where T : Drawable { if (typeof(T) == typeof(Button)) - AddUntilStep($"wait for {typeof(T).Name} enabled", () => (this.ChildrenOfType().Single() as Button)?.Enabled.Value == true); + AddUntilStep($"wait for {typeof(T).Name} enabled", () => (this.ChildrenOfType().Single() as ClickableContainer)?.Enabled.Value == true); else - AddUntilStep($"wait for {typeof(T).Name} enabled", () => this.ChildrenOfType().Single().ChildrenOfType