From 812a4b412a2c1b13a1e1215a00f863ef6fd83e45 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 19:43:23 +0900 Subject: [PATCH 01/67] Move judgement result revert logic to Playfield Previously, some judgement results were not reverted when the source DHO is not alive (e.g. frames skipped in editor). Now, all results are reverted in the exact reverse order. --- .../TestSceneCatcher.cs | 6 +-- .../UI/CatchComboDisplay.cs | 4 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 4 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 6 +-- .../Judgements/JudgementResultEntry.cs | 26 +++++++++++++ .../Objects/Drawables/DrawableHitObject.cs | 35 ++++++----------- .../Objects/HitObjectLifetimeEntry.cs | 5 +++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Rulesets/UI/HitObjectContainer.cs | 8 ---- osu.Game/Rulesets/UI/Playfield.cs | 38 ++++++++++++++++--- 11 files changed, 90 insertions(+), 50 deletions(-) create mode 100644 osu.Game/Rulesets/Judgements/JudgementResultEntry.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index e8231b07ad..11e3a5be57 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -74,12 +74,12 @@ namespace osu.Game.Rulesets.Catch.Tests }); AddStep("revert second result", () => { - catcher.OnRevertResult(drawableObject2, result2); + catcher.OnRevertResult(result2); }); checkHyperDash(true); AddStep("revert first result", () => { - catcher.OnRevertResult(drawableObject1, result1); + catcher.OnRevertResult(result1); }); checkHyperDash(false); } @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Tests checkState(CatcherAnimationState.Kiai); AddStep("revert result", () => { - catcher.OnRevertResult(drawableObject, result); + catcher.OnRevertResult(result); }); checkState(CatcherAnimationState.Idle); } diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index dbbe905879..3d0062d32f 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -63,12 +63,12 @@ namespace osu.Game.Rulesets.Catch.UI updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value); } - public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result) + public void OnRevertResult(JudgementResult result) { if (!result.Type.AffectsCombo() || !result.HasResult) return; - updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); + updateCombo(result.ComboAtJudgement, null); } private void updateCombo(int newCombo, Color4? hitObjectColour) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index c33d021876..cf7337fd0d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.UI private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) => CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result); - private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result) - => CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result); + private void onRevertResult(JudgementResult result) + => CatcherArea.OnRevertResult(result); } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 411330f6fc..1c52c092ec 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -254,7 +254,7 @@ namespace osu.Game.Rulesets.Catch.UI } } - public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result) + public void OnRevertResult(JudgementResult result) { var catchResult = (CatchJudgementResult)result; @@ -268,8 +268,8 @@ namespace osu.Game.Rulesets.Catch.UI SetHyperDashState(); } - caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject, false); - droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject, false); + caughtObjectContainer.RemoveAll(d => d.HitObject == result.HitObject, false); + droppedObjectTarget.RemoveAll(d => d.HitObject == result.HitObject, false); } /// diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 4f7535d13a..1b99270b65 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -73,10 +73,10 @@ namespace osu.Game.Rulesets.Catch.UI comboDisplay.OnNewResult(hitObject, result); } - public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result) + public void OnRevertResult(JudgementResult result) { - comboDisplay.OnRevertResult(hitObject, result); - Catcher.OnRevertResult(hitObject, result); + comboDisplay.OnRevertResult(result); + Catcher.OnRevertResult(result); } protected override void Update() diff --git a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs new file mode 100644 index 0000000000..c3f44804c3 --- /dev/null +++ b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs @@ -0,0 +1,26 @@ +// 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.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Judgements +{ + internal class JudgementResultEntry : IComparable + { + public readonly double Time; + + public readonly HitObjectLifetimeEntry HitObjectEntry; + + public readonly JudgementResult Result; + + public JudgementResultEntry(double time, HitObjectLifetimeEntry hitObjectEntry, JudgementResult result) + { + Time = time; + HitObjectEntry = hitObjectEntry; + Result = result; + } + + public int CompareTo(JudgementResultEntry? other) => Time.CompareTo(other?.Time); + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index be5a7f71e7..02fc5637d8 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -82,6 +82,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Invoked by this or a nested prior to a being reverted. /// + /// + /// This is only invoked if this is alive when the result is reverted. + /// public event Action OnRevertResult; /// @@ -222,6 +225,8 @@ namespace osu.Game.Rulesets.Objects.Drawables ensureEntryHasResult(); + entry.RevertResult += onRevertResult; + foreach (var h in HitObject.NestedHitObjects) { var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this); @@ -234,7 +239,6 @@ namespace osu.Game.Rulesets.Objects.Drawables OnNestedDrawableCreated?.Invoke(drawableNested); drawableNested.OnNewResult += onNewResult; - drawableNested.OnRevertResult += onRevertResult; drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState; // This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation(). @@ -309,7 +313,6 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var obj in nestedHitObjects) { obj.OnNewResult -= onNewResult; - obj.OnRevertResult -= onRevertResult; obj.ApplyCustomUpdateState -= onApplyCustomUpdateState; } @@ -318,6 +321,8 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject.DefaultsApplied -= onDefaultsApplied; + entry.RevertResult -= onRevertResult; + OnFree(); ParentHitObject = null; @@ -366,7 +371,11 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result); - private void onRevertResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnRevertResult?.Invoke(drawableHitObject, result); + private void onRevertResult() + { + updateState(ArmedState.Idle); + OnRevertResult?.Invoke(this, Result); + } private void onApplyCustomUpdateState(DrawableHitObject drawableHitObject, ArmedState state) => ApplyCustomUpdateState?.Invoke(drawableHitObject, state); @@ -578,26 +587,6 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - protected override void Update() - { - base.Update(); - - if (Result != null && Result.HasResult) - { - double endTime = HitObject.GetEndTime(); - - if (Result.TimeOffset + endTime > Time.Current) - { - OnRevertResult?.Invoke(this, Result); - - Result.TimeOffset = 0; - Result.Type = HitResult.None; - - updateState(ArmedState.Idle); - } - } - } - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; protected override void UpdateAfterChildren() diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index fedf419973..b517f6b9e6 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.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.Bindables; using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Judgements; @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Objects private readonly IBindable startTimeBindable = new BindableDouble(); + internal event Action? RevertResult; + /// /// Creates a new . /// @@ -95,5 +98,7 @@ namespace osu.Game.Rulesets.Objects /// Set using . /// internal void SetInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + + internal void OnRevertResult() => RevertResult?.Invoke(); } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 71b452c309..8edc5517cb 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.UI playfield = new Lazy(() => CreatePlayfield().With(p => { p.NewResult += (_, r) => NewResult?.Invoke(r); - p.RevertResult += (_, r) => RevertResult?.Invoke(r); + p.RevertResult += r => RevertResult?.Invoke(r); })); } diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 7cbf49aa31..099be486b3 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -28,11 +28,6 @@ namespace osu.Game.Rulesets.UI /// public event Action NewResult; - /// - /// Invoked when a judgement is reverted. - /// - public event Action RevertResult; - /// /// Invoked when a becomes used by a . /// @@ -111,7 +106,6 @@ namespace osu.Game.Rulesets.UI private void addDrawable(DrawableHitObject drawable) { drawable.OnNewResult += onNewResult; - drawable.OnRevertResult += onRevertResult; bindStartTime(drawable); AddInternal(drawable); @@ -120,7 +114,6 @@ namespace osu.Game.Rulesets.UI private void removeDrawable(DrawableHitObject drawable) { drawable.OnNewResult -= onNewResult; - drawable.OnRevertResult -= onRevertResult; unbindStartTime(drawable); @@ -154,7 +147,6 @@ namespace osu.Game.Rulesets.UI #endregion private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r); - private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r); #region Comparator + StartTime tracking diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index a7881678f1..9535ebb9ed 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -22,6 +22,8 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osu.Game.Rulesets.Objects.Pooling; +using osu.Game.Rulesets.Scoring; +using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Rulesets.UI { @@ -35,9 +37,9 @@ namespace osu.Game.Rulesets.UI public event Action NewResult; /// - /// Invoked when a judgement is reverted. + /// Invoked when a judgement result is reverted. /// - public event Action RevertResult; + public event Action RevertResult; /// /// The contained in this Playfield. @@ -98,6 +100,8 @@ namespace osu.Game.Rulesets.UI private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager(); + private readonly Stack judgementResults; + /// /// Creates a new . /// @@ -107,14 +111,15 @@ namespace osu.Game.Rulesets.UI hitObjectContainerLazy = new Lazy(() => CreateHitObjectContainer().With(h => { - h.NewResult += (d, r) => NewResult?.Invoke(d, r); - h.RevertResult += (d, r) => RevertResult?.Invoke(d, r); + h.NewResult += onNewResult; h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o); h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); })); entryManager.OnEntryAdded += onEntryAdded; entryManager.OnEntryRemoved += onEntryRemoved; + + judgementResults = new Stack(); } [BackgroundDependencyLoader] @@ -224,7 +229,7 @@ namespace osu.Game.Rulesets.UI otherPlayfield.DisplayJudgements.BindTo(DisplayJudgements); otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r); - otherPlayfield.RevertResult += (d, r) => RevertResult?.Invoke(d, r); + otherPlayfield.RevertResult += r => RevertResult?.Invoke(r); otherPlayfield.HitObjectUsageBegan += h => HitObjectUsageBegan?.Invoke(h); otherPlayfield.HitObjectUsageFinished += h => HitObjectUsageFinished?.Invoke(h); @@ -252,6 +257,10 @@ namespace osu.Game.Rulesets.UI updatable.Update(this); } } + + // When rewinding, revert future judgements in the reverse order. + while (judgementResults.Count > 0 && Time.Current < judgementResults.Peek().Time) + revertResult(judgementResults.Pop()); } /// @@ -443,6 +452,25 @@ namespace osu.Game.Rulesets.UI #endregion + private void onNewResult(DrawableHitObject drawable, JudgementResult result) + { + // Not using result.TimeAbsolute because that might change and also there is a potential precision issue. + judgementResults.Push(new JudgementResultEntry(Time.Current, drawable.Entry.AsNonNull(), result)); + + NewResult?.Invoke(drawable, result); + } + + private void revertResult(JudgementResultEntry entry) + { + var result = entry.Result; + RevertResult?.Invoke(result); + + result.TimeOffset = 0; + result.Type = HitResult.None; + + entry.HitObjectEntry.OnRevertResult(); + } + #region Editor logic /// From 32acaa44be3009b3120e05753308e157c8f0219a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 19:52:37 +0900 Subject: [PATCH 02/67] Remove now-redundant code --- .../TestSceneCatcher.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 11e3a5be57..f60ae29f77 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -60,17 +60,15 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestCatcherHyperStateReverted() { - DrawableCatchHitObject drawableObject1 = null; - DrawableCatchHitObject drawableObject2 = null; JudgementResult result1 = null; JudgementResult result2 = null; AddStep("catch hyper fruit", () => { - attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1); + result1 = attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }); }); AddStep("catch normal fruit", () => { - attemptCatch(new Fruit(), out drawableObject2, out result2); + result2 = attemptCatch(new Fruit()); }); AddStep("revert second result", () => { @@ -87,11 +85,10 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestCatcherAnimationStateReverted() { - DrawableCatchHitObject drawableObject = null; JudgementResult result = null; AddStep("catch kiai fruit", () => { - attemptCatch(new TestKiaiFruit(), out drawableObject, out result); + result = attemptCatch(new TestKiaiFruit()); }); checkState(CatcherAnimationState.Kiai); AddStep("revert result", () => @@ -268,23 +265,19 @@ namespace osu.Game.Rulesets.Catch.Tests private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state); - private void attemptCatch(CatchHitObject hitObject) - { - attemptCatch(() => hitObject, 1); - } - private void attemptCatch(Func hitObject, int count) { for (int i = 0; i < count; i++) - attemptCatch(hitObject(), out _, out _); + attemptCatch(hitObject()); } - private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result) + private JudgementResult attemptCatch(CatchHitObject hitObject) { hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableObject = createDrawableObject(hitObject); - result = createResult(hitObject); + var drawableObject = createDrawableObject(hitObject); + var result = createResult(hitObject); applyResult(drawableObject, result); + return result; } private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result) From 11e1b22bf5be695bebc5dad23ea9c2aa268d4be7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 20:53:35 +0900 Subject: [PATCH 03/67] Move MaximumJudgementOffset to HitObject We want to access this property for computing lifetime --- .../Objects/Drawables/DrawableHoldNote.cs | 2 -- .../Objects/Drawables/DrawableHoldNoteTail.cs | 11 +---------- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 ++ osu.Game.Rulesets.Mania/Objects/TailNote.cs | 9 +++++++++ .../Objects/Drawables/DrawableSpinnerTick.cs | 2 -- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 7 +++++++ .../Objects/Drawables/DrawableDrumRollTick.cs | 2 -- osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs | 2 ++ .../Objects/Drawables/DrawableHitObject.cs | 14 +------------- osu.Game/Rulesets/Objects/HitObject.cs | 8 ++++++++ 11 files changed, 32 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 759d2346e8..8863de5ee3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private double? releaseTime; - public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset; - public DrawableHoldNote() : this(null) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index bf477277c6..20ea962994 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -15,13 +15,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public partial class DrawableHoldNoteTail : DrawableNote { - /// - /// Lenience of release hit windows. This is to make cases where the hold note release - /// is timed alongside presses of other hit objects less awkward. - /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps - /// - private const double release_window_lenience = 1.5; - protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail; protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject; @@ -40,14 +33,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public void UpdateResult() => base.UpdateResult(true); - public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience; - protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); // Factor in the release lenience - timeOffset /= release_window_lenience; + timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE; if (!userTriggered) { diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 22fab15c1b..c367886efe 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Mania.Objects /// public TailNote Tail { get; private set; } + public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset; + /// /// The time between ticks of this hold. /// diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index cda8e2fa31..d6dc25079a 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs @@ -10,6 +10,15 @@ namespace osu.Game.Rulesets.Mania.Objects { public class TailNote : Note { + /// + /// Lenience of release hit windows. This is to make cases where the hold note release + /// is timed alongside presses of other hit objects less awkward. + /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps + /// + public const double RELEASE_WINDOW_LENIENCE = 1.5; + public override Judgement CreateJudgement() => new ManiaJudgement(); + + public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index b9ce07363c..34253e3d4f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; } - public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; - /// /// Apply a judgement result. /// diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 0e1fe56cea..ed6f8a9a6a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -71,8 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; AddNested(i < SpinsRequired - ? new SpinnerTick { StartTime = startTime } - : new SpinnerBonusTick { StartTime = startTime }); + ? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration } + : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration }); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index 650d02c675..c890f3771b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerTick : OsuHitObject { + /// + /// Duration of the containing this spinner tick. + /// + public double SpinnerDuration { get; set; } + public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + public override double MaximumJudgementOffset => SpinnerDuration; + public class OsuSpinnerTickJudgement : OsuJudgement { public override HitResult MaxResult => HitResult.SmallBonus; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 45e25ee7dc..abecd19545 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), _ => new TickPiece()); - public override double MaximumJudgementOffset => HitObject.HitWindow; - protected override void OnApply() { base.OnApply(); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 433fdab908..6bcb8674e6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; + public override double MaximumJudgementOffset => HitWindow; + protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime }; public class StrongNestedHit : StrongNestedHitObject diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index be5a7f71e7..43431d7703 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -651,18 +651,6 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } - /// - /// The maximum offset from the end time of at which this can be judged. - /// The time offset of will be clamped to this value during . - /// - /// Defaults to the miss window of . - /// - /// - /// - /// This does not affect the time offset provided to invocations of . - /// - public virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0; - /// /// Applies the of this , notifying responders such as /// the of the . @@ -684,7 +672,7 @@ namespace osu.Game.Rulesets.Objects.Drawables $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } - Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); + Result.TimeOffset = Math.Min(HitObject.MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 0f79e58201..25f538d211 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -200,6 +200,14 @@ namespace osu.Game.Rulesets.Objects [NotNull] protected virtual HitWindows CreateHitWindows() => new HitWindows(); + /// + /// The maximum offset from the end time of at which this can be judged. + /// + /// Defaults to the miss window. + /// + /// + public virtual double MaximumJudgementOffset => HitWindows?.WindowFor(HitResult.Miss) ?? 0; + public IList CreateSlidingSamples() { var slidingSamples = new List(); From d8f9b7d02f2a4e3b86976b45226f7488ef1639f8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 21:25:21 +0900 Subject: [PATCH 04/67] Use MaximumJudgementOffset for lifetime --- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 3559a1521c..b93a427196 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -232,8 +232,7 @@ namespace osu.Game.Rulesets.UI.Scrolling double computedStartTime = computeDisplayStartTime(entry); // always load the hitobject before its first judgement offset - double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0; - entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime); + entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - entry.HitObject.MaximumJudgementOffset, computedStartTime); } private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null) From 75a1a2ec2f2d136a510f05c2371b6d37fc8270a9 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 22 Jan 2023 03:44:59 +0100 Subject: [PATCH 05/67] Hide `ResumeOverlay` when `OsuModAutopilot` is enabled --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 1 + osu.Game/Rulesets/Mods/Mod.cs | 3 +++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 6772cfe0be..99a35f4d69 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 0.1; + public override bool HidesResumeOverlay => true; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 04d55bc5fe..4f85bf4663 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -107,6 +107,9 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; + [JsonIgnore] + public virtual bool HidesResumeOverlay => false; + /// /// The mods this mod cannot be enabled with. /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 71b452c309..38dbb1308f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -230,7 +230,9 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + if (ResumeOverlay != null + && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) + && Mods.All(mod => !mod.HidesResumeOverlay)) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; From 8405a3e1722f0b3b3d11d2ff33a08fb1dfdafb56 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 23 Jan 2023 18:51:55 +0900 Subject: [PATCH 06/67] Add test for RevertResult --- .../Gameplay/TestScenePoolingRuleset.cs | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 55ee6c9fc9..22ab74cc30 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -19,7 +19,6 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -37,6 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay private TestDrawablePoolingRuleset drawableRuleset; + private TestPlayfield playfield => (TestPlayfield)drawableRuleset.Playfield; + [Test] public void TestReusedWithHitObjectsSpacedFarApart() { @@ -133,29 +134,49 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("no DHOs shown", () => !this.ChildrenOfType().Any()); } + [Test] + public void TestRevertResult() + { + ManualClock clock = null; + Beatmap beatmap; + + createTest(beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject { StartTime = 0 }, + new TestHitObject { StartTime = 500 }, + new TestHitObject { StartTime = 1000 }, + } + }, 10, () => new FramedClock(clock = new ManualClock())); + + AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100); + AddUntilStep("all judged", () => playfield.JudgedObjects.Count == 3); + + AddStep("rewind to middle", () => clock.CurrentTime = beatmap.HitObjects[1].StartTime - 100); + AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count == 1); + + AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100); + AddUntilStep("all judged", () => playfield.JudgedObjects.Count == 3); + + AddStep("disable frame stability", () => drawableRuleset.FrameStablePlayback = false); + AddStep("instant seek to start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime - 100); + AddAssert("all results reverted", () => playfield.JudgedObjects.Count == 0); + } + [Test] public void TestApplyHitResultOnKilled() { ManualClock clock = null; - bool anyJudged = false; - - void onNewResult(JudgementResult _) => anyJudged = true; var beatmap = new Beatmap(); beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 }); createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock())); - AddStep("subscribe to new result", () => - { - anyJudged = false; - drawableRuleset.NewResult += onNewResult; - }); AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000); - AddAssert("object judged", () => anyJudged); - - AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult); + AddAssert("object judged", () => playfield.JudgedObjects.Count == 1); } private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null) @@ -212,12 +233,24 @@ namespace osu.Game.Tests.Visual.Gameplay private partial class TestPlayfield : Playfield { + public readonly HashSet JudgedObjects = new HashSet(); + private readonly int poolSize; public TestPlayfield(int poolSize) { this.poolSize = poolSize; AddInternal(HitObjectContainer); + NewResult += (_, r) => + { + Assert.That(JudgedObjects, Has.No.Member(r.HitObject)); + JudgedObjects.Add(r.HitObject); + }; + RevertResult += r => + { + Assert.That(JudgedObjects, Has.Member(r.HitObject)); + JudgedObjects.Remove(r.HitObject); + }; } [BackgroundDependencyLoader] From 8b47af6503b5a93e98940d31b96d13b1858361f5 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 24 Jan 2023 00:49:09 +0100 Subject: [PATCH 07/67] Remove `HidesResumeOverlay` and set `ResumeOverlay` to `null` in `OsuModAutopilot` --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 ++- osu.Game/Rulesets/Mods/Mod.cs | 3 --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 99a35f4d69..276724c169 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 0.1; - public override bool HidesResumeOverlay => true; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; @@ -55,6 +54,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + drawableRuleset.ResumeOverlay = null; + // Grab the input manager to disable the user's cursor, and for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; inputManager.AllowUserCursorMovement = false; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 4f85bf4663..04d55bc5fe 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -107,9 +107,6 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; - [JsonIgnore] - public virtual bool HidesResumeOverlay => false; - /// /// The mods this mod cannot be enabled with. /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 38dbb1308f..403dee5651 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -230,9 +230,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null - && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) - && Mods.All(mod => !mod.HidesResumeOverlay)) + if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; @@ -507,7 +505,7 @@ namespace osu.Game.Rulesets.UI /// /// An optional overlay used when resuming gameplay from a paused state. /// - public ResumeOverlay ResumeOverlay { get; protected set; } + public ResumeOverlay ResumeOverlay { get; set; } /// /// Returns first available provided by a . From e66e43e17ce3330f5924738a28de44f6593c2bc2 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Jan 2023 14:15:17 +0900 Subject: [PATCH 08/67] Remove unused code --- osu.Game/Rulesets/Judgements/JudgementResultEntry.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs index c3f44804c3..b9d75d3acb 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Judgements { - internal class JudgementResultEntry : IComparable + internal class JudgementResultEntry { public readonly double Time; @@ -20,7 +19,5 @@ namespace osu.Game.Rulesets.Judgements HitObjectEntry = hitObjectEntry; Result = result; } - - public int CompareTo(JudgementResultEntry? other) => Time.CompareTo(other?.Time); } } \ No newline at end of file From cc87923179ccaa9533be0a2501e31f95e6aba6c8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Jan 2023 14:19:24 +0900 Subject: [PATCH 09/67] Fix OnRevertResult timing --- osu.Game/Rulesets/UI/Playfield.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 9535ebb9ed..1d9390ea14 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -464,11 +464,10 @@ namespace osu.Game.Rulesets.UI { var result = entry.Result; RevertResult?.Invoke(result); + entry.HitObjectEntry.OnRevertResult(); result.TimeOffset = 0; result.Type = HitResult.None; - - entry.HitObjectEntry.OnRevertResult(); } #region Editor logic From efef97d5be5f9bcf38c67713c572f3e48c0c5625 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Jan 2023 15:35:06 +0900 Subject: [PATCH 10/67] Store Result.TimeAbsolute separately from offset Calculating from TimeOffset is bad because it loses precision. The result time won't change anymore even If `HitObject.GetEndTime()` changes later. --- osu.Game/Rulesets/Judgements/JudgementResult.cs | 17 ++++++++++++++--- .../Rulesets/Judgements/JudgementResultEntry.cs | 5 ++--- .../Objects/Drawables/DrawableHitObject.cs | 3 ++- osu.Game/Rulesets/UI/Playfield.cs | 5 ++--- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 2685cb4e2a..9fdbdae396 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -33,16 +33,19 @@ namespace osu.Game.Rulesets.Judgements public readonly Judgement Judgement; /// - /// The offset from a perfect hit at which this occurred. + /// The offset of from the end time of , clamped by . /// Populated when this is applied via . /// public double TimeOffset { get; internal set; } /// /// The absolute time at which this occurred. - /// Equal to the (end) time of the + . + /// Populated when this is applied via . /// - public double TimeAbsolute => HitObject.GetEndTime() + TimeOffset; + /// + /// This is initially set to the end time of . + /// + public double TimeAbsolute { get; internal set; } /// /// The combo prior to this occurring. @@ -83,6 +86,14 @@ namespace osu.Game.Rulesets.Judgements { HitObject = hitObject; Judgement = judgement; + Reset(); + } + + internal void Reset() + { + Type = HitResult.None; + TimeOffset = 0; + TimeAbsolute = HitObject.GetEndTime(); } public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})"; diff --git a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs index b9d75d3acb..09351e6974 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs @@ -7,15 +7,14 @@ namespace osu.Game.Rulesets.Judgements { internal class JudgementResultEntry { - public readonly double Time; + public double Time => Result.TimeAbsolute; public readonly HitObjectLifetimeEntry HitObjectEntry; public readonly JudgementResult Result; - public JudgementResultEntry(double time, HitObjectLifetimeEntry hitObjectEntry, JudgementResult result) + public JudgementResultEntry(HitObjectLifetimeEntry hitObjectEntry, JudgementResult result) { - Time = time; HitObjectEntry = hitObjectEntry; Result = result; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 02fc5637d8..0c59d638b6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -673,7 +673,8 @@ namespace osu.Game.Rulesets.Objects.Drawables $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } - Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); + Result.TimeAbsolute = Time.Current; + Result.TimeOffset = Math.Min(MaximumJudgementOffset, Result.TimeAbsolute - HitObject.GetEndTime()); if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 1d9390ea14..40e84a60e3 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -455,7 +455,7 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { // Not using result.TimeAbsolute because that might change and also there is a potential precision issue. - judgementResults.Push(new JudgementResultEntry(Time.Current, drawable.Entry.AsNonNull(), result)); + judgementResults.Push(new JudgementResultEntry(drawable.Entry.AsNonNull(), result)); NewResult?.Invoke(drawable, result); } @@ -466,8 +466,7 @@ namespace osu.Game.Rulesets.UI RevertResult?.Invoke(result); entry.HitObjectEntry.OnRevertResult(); - result.TimeOffset = 0; - result.Type = HitResult.None; + result.Reset(); } #region Editor logic From e1702a8ee9d50cf15f1a2cd02a64d03a245a0d3f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Jan 2023 15:43:57 +0900 Subject: [PATCH 11/67] Fix inspection issue --- osu.Game/Rulesets/UI/Playfield.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 40e84a60e3..3ec31db4b9 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osu.Game.Rulesets.Objects.Pooling; -using osu.Game.Rulesets.Scoring; using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Rulesets.UI From e3a5c233e9b8510e6e199eeabf39de0ad8a5145b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 17:39:35 +0900 Subject: [PATCH 12/67] Update test to use newer assetion logic --- osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 22ab74cc30..0469df1de3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -151,17 +151,17 @@ namespace osu.Game.Tests.Visual.Gameplay }, 10, () => new FramedClock(clock = new ManualClock())); AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100); - AddUntilStep("all judged", () => playfield.JudgedObjects.Count == 3); + AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3)); AddStep("rewind to middle", () => clock.CurrentTime = beatmap.HitObjects[1].StartTime - 100); - AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count == 1); + AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(1)); AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100); - AddUntilStep("all judged", () => playfield.JudgedObjects.Count == 3); + AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3)); AddStep("disable frame stability", () => drawableRuleset.FrameStablePlayback = false); AddStep("instant seek to start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime - 100); - AddAssert("all results reverted", () => playfield.JudgedObjects.Count == 0); + AddAssert("all results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0)); } [Test] From 27578c48f5fd43a42cde43acb290c10dac9c3fe4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Jan 2023 19:35:44 +0900 Subject: [PATCH 13/67] Remove JudgementResultEntry It is not needed anymore as TimeAbsolute is stored raw. --- .../Judgements/JudgementResultEntry.cs | 22 ------------------- osu.Game/Rulesets/UI/Playfield.cs | 18 ++++++++------- 2 files changed, 10 insertions(+), 30 deletions(-) delete mode 100644 osu.Game/Rulesets/Judgements/JudgementResultEntry.cs diff --git a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs deleted file mode 100644 index 09351e6974..0000000000 --- a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Judgements -{ - internal class JudgementResultEntry - { - public double Time => Result.TimeAbsolute; - - public readonly HitObjectLifetimeEntry HitObjectEntry; - - public readonly JudgementResult Result; - - public JudgementResultEntry(HitObjectLifetimeEntry hitObjectEntry, JudgementResult result) - { - HitObjectEntry = hitObjectEntry; - Result = result; - } - } -} \ No newline at end of file diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 3ec31db4b9..5d6a8de33c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.UI private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager(); - private readonly Stack judgementResults; + private readonly Stack judgedEntries; /// /// Creates a new . @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.UI entryManager.OnEntryAdded += onEntryAdded; entryManager.OnEntryRemoved += onEntryRemoved; - judgementResults = new Stack(); + judgedEntries = new Stack(); } [BackgroundDependencyLoader] @@ -258,8 +258,8 @@ namespace osu.Game.Rulesets.UI } // When rewinding, revert future judgements in the reverse order. - while (judgementResults.Count > 0 && Time.Current < judgementResults.Peek().Time) - revertResult(judgementResults.Pop()); + while (judgedEntries.Count > 0 && Time.Current < judgedEntries.Peek().Result.AsNonNull().TimeAbsolute) + revertResult(judgedEntries.Pop()); } /// @@ -453,17 +453,19 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { - // Not using result.TimeAbsolute because that might change and also there is a potential precision issue. - judgementResults.Push(new JudgementResultEntry(drawable.Entry.AsNonNull(), result)); + Debug.Assert(result != null && drawable.Entry?.Result == result); + judgedEntries.Push(drawable.Entry.AsNonNull()); NewResult?.Invoke(drawable, result); } - private void revertResult(JudgementResultEntry entry) + private void revertResult(HitObjectLifetimeEntry entry) { var result = entry.Result; + Debug.Assert(result != null); + RevertResult?.Invoke(result); - entry.HitObjectEntry.OnRevertResult(); + entry.OnRevertResult(); result.Reset(); } From 258de3b2d89cc2a7a4a624202c8460be89e83638 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 9 Feb 2023 17:15:37 +0900 Subject: [PATCH 14/67] Store RawTime in JudgementResult --- .../Rulesets/Judgements/JudgementResult.cs | 33 ++++++++++++------- .../Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Rulesets/UI/Playfield.cs | 12 +++++-- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 6749fd7932..bf29919e34 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using JetBrains.Annotations; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -33,19 +34,30 @@ namespace osu.Game.Rulesets.Judgements public readonly Judgement Judgement; /// - /// The offset of from the end time of , clamped by . - /// Populated when this is applied via . - /// - public double TimeOffset { get; internal set; } - - /// - /// The absolute time at which this occurred. + /// The time at which this occurred. /// Populated when this is applied via . /// /// - /// This is initially set to the end time of . + /// This is used instead of to check whether this should be reverted. /// - public double TimeAbsolute { get; internal set; } + internal double? RawTime { get; set; } + + /// + /// The offset of from the end time of , clamped by . + /// + public double TimeOffset + { + get => RawTime != null ? Math.Min(RawTime.Value - HitObject.GetEndTime(), HitObject.MaximumJudgementOffset) : 0; + internal set => RawTime = HitObject.GetEndTime() + value; + } + + /// + /// The absolute time at which this occurred, clamped by the end time of plus . + /// + /// + /// The end time of is returned if this result is not populated yet. + /// + public double TimeAbsolute => RawTime != null ? Math.Min(RawTime.Value, HitObject.GetEndTime() + HitObject.MaximumJudgementOffset) : HitObject.GetEndTime(); /// /// The combo prior to this occurring. @@ -92,8 +104,7 @@ namespace osu.Game.Rulesets.Judgements internal void Reset() { Type = HitResult.None; - TimeOffset = 0; - TimeAbsolute = HitObject.GetEndTime(); + RawTime = null; } public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})"; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d30fc00bfc..f7c6340419 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -661,7 +661,7 @@ namespace osu.Game.Rulesets.Objects.Drawables $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } - Result.TimeOffset = Math.Min(HitObject.MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); + Result.RawTime = Time.Current; if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 5d6a8de33c..b1c3b78e67 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -258,8 +258,16 @@ namespace osu.Game.Rulesets.UI } // When rewinding, revert future judgements in the reverse order. - while (judgedEntries.Count > 0 && Time.Current < judgedEntries.Peek().Result.AsNonNull().TimeAbsolute) + while (judgedEntries.Count > 0) + { + var result = judgedEntries.Peek().Result; + Debug.Assert(result?.RawTime != null); + + if (Time.Current >= result.RawTime.Value) + break; + revertResult(judgedEntries.Pop()); + } } /// @@ -453,7 +461,7 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { - Debug.Assert(result != null && drawable.Entry?.Result == result); + Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null); judgedEntries.Push(drawable.Entry.AsNonNull()); NewResult?.Invoke(drawable, result); From 5ddaf8ea3c2d0f79f891a1a8672e534e3e2dc13e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 9 Feb 2023 17:43:04 +0900 Subject: [PATCH 15/67] Fix test setting invalid TimeOffset --- .../Visual/Gameplay/TestSceneUnstableRateCounter.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index 2f572b46c9..d0e516ed39 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -22,12 +22,18 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(ScoreProcessor))] private TestScoreProcessor scoreProcessor = new TestScoreProcessor(); - private readonly OsuHitWindows hitWindows = new OsuHitWindows(); + private readonly OsuHitWindows hitWindows; private UnstableRateCounter counter; private double prev; + public TestSceneUnstableRateCounter() + { + hitWindows = new OsuHitWindows(); + hitWindows.SetDifficulty(5); + } + [SetUpSteps] public void SetUp() { From e4b84ebd0b3a89e56c07da34195862bccfbbb38c Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 13 Feb 2023 23:51:39 +0100 Subject: [PATCH 16/67] Add `UseResumeOverlay` and use it for hiding the `ResumeOverlay` --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 4 ++-- osu.Game/Rulesets/UI/DrawableRuleset.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 276724c169..9eb0a46bfb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -54,14 +54,14 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.ResumeOverlay = null; - // Grab the input manager to disable the user's cursor, and for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; inputManager.AllowUserCursorMovement = false; // Generate the replay frames the cursor should follow replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast().ToList(); + + drawableRuleset.UseResumeOverlay = false; } } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 403dee5651..23e8d1fc2d 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -196,6 +196,8 @@ namespace osu.Game.Rulesets.UI if ((ResumeOverlay = CreateResumeOverlay()) != null) { + UseResumeOverlay = true; + AddInternal(CreateInputManager() .WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(ResumeOverlay))); @@ -230,7 +232,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + if (ResumeOverlay != null && UseResumeOverlay && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; @@ -507,6 +509,12 @@ namespace osu.Game.Rulesets.UI /// public ResumeOverlay ResumeOverlay { get; set; } + /// + /// Whether the should be used to return the user's cursor position to its previous location after a pause. + /// + /// Defaults to true if a ruleset returns a non-null overlay via . + public bool UseResumeOverlay { get; set; } + /// /// Returns first available provided by a . /// From a820c0c8ebdc7d1e6ccb5e6a59d0cb5c50851d51 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 13 Feb 2023 23:55:13 +0100 Subject: [PATCH 17/67] Add `TestSceneInstantResume` --- .../TestSceneInstantResume.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs new file mode 100644 index 0000000000..2bc6386185 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneInstantResume : TestSceneOsuPlayer + { + protected override bool HasCustomSteps => true; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = new[] { new OsuModAutopilot() }; + return new TestPlayer(); + } + + [Test] + public void TestInstantResume() + { + CreateTest(); + + AddStep("press pause", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value); + AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape)); + AddStep("press resume", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait for resume", () => !Player.IsResuming); + AddAssert("resumed", () => !Player.GameplayClockContainer.IsPaused.Value); + } + } +} From 9e04a36d86c9a19e37cf470aa4c4505fa9ad4ef9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Feb 2023 15:07:45 +0900 Subject: [PATCH 18/67] Move test to a mod test and add more resilient test logic --- .../TestSceneOsuModAutopilot.cs} | 21 ++++++++----------- osu.Game/Tests/Visual/ModTestScene.cs | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneInstantResume.cs => Mods/TestSceneOsuModAutopilot.cs} (66%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs similarity index 66% rename from osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs rename to osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs index 2bc6386185..37b31d1d1a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs @@ -3,26 +3,23 @@ using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Tests.Visual; using osuTK.Input; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Mods { - public partial class TestSceneInstantResume : TestSceneOsuPlayer + public partial class TestSceneOsuModAutopilot : OsuModTestScene { - protected override bool HasCustomSteps => true; - - protected override TestPlayer CreatePlayer(Ruleset ruleset) - { - SelectedMods.Value = new[] { new OsuModAutopilot() }; - return new TestPlayer(); - } - [Test] public void TestInstantResume() { - CreateTest(); + CreateModTest(new ModTestData + { + Mod = new OsuModAutopilot(), + PassCondition = () => true, + Autoplay = false, + }); + AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value); AddStep("press pause", () => InputManager.PressKey(Key.Escape)); AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value); AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape)); diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 0559d80384..aa5b506343 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual protected override bool CheckModsAllowFailure() => allowFail; public ModTestPlayer(ModTestData data, bool allowFail) - : base(false, false) + : base(true, false) { this.allowFail = allowFail; currentTestData = data; From 63f349876290a3d5c07768e75116ae132dbbc2ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Feb 2023 15:11:33 +0900 Subject: [PATCH 19/67] Restructure `UseResumeOverlay` to correctly handle a value change before BDL load --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 23e8d1fc2d..c839062875 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -194,13 +194,16 @@ namespace osu.Game.Rulesets.UI Audio = audioContainer; - if ((ResumeOverlay = CreateResumeOverlay()) != null) + if (UseResumeOverlay) { - UseResumeOverlay = true; - - AddInternal(CreateInputManager() - .WithChild(CreatePlayfieldAdjustmentContainer() - .WithChild(ResumeOverlay))); + if ((ResumeOverlay = CreateResumeOverlay()) == null) + UseResumeOverlay = false; + else + { + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); + } } applyRulesetMods(Mods, config); @@ -512,8 +515,8 @@ namespace osu.Game.Rulesets.UI /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. /// - /// Defaults to true if a ruleset returns a non-null overlay via . - public bool UseResumeOverlay { get; set; } + /// Defaults to true, but will become false if the ruleset fails to return a non-null overlay via . + public bool UseResumeOverlay { get; set; } = true; /// /// Returns first available provided by a . From 19e3c5d33c5495cc17180932806db5e3a1c34c14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 13:59:24 +0900 Subject: [PATCH 20/67] Adjust song select background dimming to be more evenly applied --- osu.Game/Screens/Select/SongSelect.cs | 2 ++ osu.Game/Screens/Select/WedgeBackground.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8786821c77..5c4a42852c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -767,6 +767,8 @@ namespace osu.Game.Screens.Select { backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; + backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = 0.4f; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); diff --git a/osu.Game/Screens/Select/WedgeBackground.cs b/osu.Game/Screens/Select/WedgeBackground.cs index da12c1a67a..fd4b91bf5f 100644 --- a/osu.Game/Screens/Select/WedgeBackground.cs +++ b/osu.Game/Screens/Select/WedgeBackground.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Size = new Vector2(1, 0.5f), - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(0.2f), Shear = new Vector2(0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Select RelativePositionAxes = Axes.Y, Size = new Vector2(1, -0.5f), Position = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(0.2f), Shear = new Vector2(-0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, From 9ed068c1e67e870f49163620cf95697c5426a108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 14:16:34 +0900 Subject: [PATCH 21/67] Only apply dim changes when background blur is disabled --- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 16 +++++++++++++--- osu.Game/Screens/Select/WedgeBackground.cs | 9 +++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 25830c9d54..6f6292c3b2 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers /// public const float BREAK_LIGHTEN_AMOUNT = 0.3f; - protected const double BACKGROUND_FADE_DURATION = 800; + public const double BACKGROUND_FADE_DURATION = 800; /// /// Whether or not user-configured settings relating to brightness of elements should be ignored diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5c4a42852c..8546dfeeaa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -31,6 +31,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -148,7 +149,7 @@ namespace osu.Game.Screens.Select if (!this.IsCurrentScreen()) return; - ApplyToBackground(b => b.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0); + ApplyToBackground(applyBlurToBackground); }); LoadComponentAsync(Carousel = new BeatmapCarousel @@ -194,6 +195,7 @@ namespace osu.Game.Screens.Select { ParallaxAmount = 0.005f, RelativeSizeAxes = Axes.Both, + Alpha = 0, Anchor = Anchor.Centre, Origin = Anchor.Centre, Child = new WedgeBackground @@ -766,10 +768,10 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; backgroundModeBeatmap.IgnoreUserSettings.Value = true; - backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = 0.4f; backgroundModeBeatmap.FadeColour(Color4.White, 250); + + applyBlurToBackground(backgroundModeBeatmap); }); beatmapInfoWedge.Beatmap = beatmap; @@ -787,6 +789,14 @@ namespace osu.Game.Screens.Select } } + private void applyBlurToBackground(BackgroundScreenBeatmap backgroundModeBeatmap) + { + backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; + backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = configBackgroundBlur.Value ? 0 : 0.4f; + + wedgeBackground.FadeTo(configBackgroundBlur.Value ? 0.5f : 0.2f, UserDimContainer.BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + private readonly WeakReference lastTrack = new WeakReference(null); /// diff --git a/osu.Game/Screens/Select/WedgeBackground.cs b/osu.Game/Screens/Select/WedgeBackground.cs index fd4b91bf5f..2e2b43cd70 100644 --- a/osu.Game/Screens/Select/WedgeBackground.cs +++ b/osu.Game/Screens/Select/WedgeBackground.cs @@ -1,14 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osuTK; using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Select { @@ -22,7 +19,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Size = new Vector2(1, 0.5f), - Colour = Color4.Black.Opacity(0.2f), + Colour = Color4.Black, Shear = new Vector2(0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, @@ -32,7 +29,7 @@ namespace osu.Game.Screens.Select RelativePositionAxes = Axes.Y, Size = new Vector2(1, -0.5f), Position = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.2f), + Colour = Color4.Black, Shear = new Vector2(-0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, From e71dfd75554c7d162c3d1fa8036859044469e1b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 16:11:16 +0900 Subject: [PATCH 22/67] Fix skin export failing if a directory exists with the proposed filename --- osu.Game/Database/LegacyExporter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 09d6913dd9..7fe58240a2 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Utils; @@ -43,7 +44,10 @@ namespace osu.Game.Database { string itemFilename = GetFilename(item).GetValidFilename(); - IEnumerable existingExports = exportStorage.GetFiles("", $"{itemFilename}*{FileExtension}"); + IEnumerable existingExports = + exportStorage + .GetFiles(string.Empty, $"{itemFilename}*{FileExtension}") + .Concat(exportStorage.GetDirectories(string.Empty)); string filename = NamingUtils.GetNextBestFilename(existingExports, $"{itemFilename}{FileExtension}"); using (var stream = exportStorage.CreateFileSafely(filename)) From 8bbd00822c6ebad41fa036b05ccd966d100a48dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 17:24:34 +0900 Subject: [PATCH 23/67] Simplify and rename `SkinnableTargetComponentsContainer` --- .../Legacy/CatchLegacySkinTransformer.cs | 3 ++- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 4 ++-- osu.Game/Skinning/ArgonSkin.cs | 4 ++-- ...r.cs => DefaultSkinComponentsContainer.cs} | 23 +++++-------------- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/Skin.cs | 4 +++- osu.Game/Skinning/SkinnableTargetContainer.cs | 10 ++++---- osu.Game/Skinning/TrianglesSkin.cs | 4 ++-- 8 files changed, 24 insertions(+), 30 deletions(-) rename osu.Game/Skinning/{SkinnableTargetComponentsContainer.cs => DefaultSkinComponentsContainer.cs} (61%) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 08ac55508a..a06435583b 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Skinning; using osuTK.Graphics; @@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (targetComponent.Lookup) { case GlobalSkinComponentLookup.LookupType.MainHUDComponents: - var components = base.GetDrawableComponent(lookup) as SkinnableTargetComponentsContainer; + var components = base.GetDrawableComponent(lookup) as Container; if (providesComboCounter && components != null) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 5cd8c00935..943f1d31ba 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -58,14 +58,14 @@ namespace osu.Game.Tests.Visual.Gameplay protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource) { var actualComponentsContainer = Player.ChildrenOfType().First(s => s.Target == target) - .ChildrenOfType().SingleOrDefault(); + .ChildrenOfType().SingleOrDefault(); if (actualComponentsContainer == null) return false; var actualInfo = actualComponentsContainer.CreateSkinnableInfo(); - var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)); + var expectedComponentsContainer = (DefaultSkinComponentsContainer)expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)); if (expectedComponentsContainer == null) return false; diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 53c6c1e5ce..71f0888c6f 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -94,7 +94,7 @@ namespace osu.Game.Skinning switch (globalLookup.Lookup) { case GlobalSkinComponentLookup.LookupType.SongSelect: - var songSelectComponents = new SkinnableTargetComponentsContainer(_ => + var songSelectComponents = new DefaultSkinComponentsContainer(_ => { // do stuff when we need to. }); @@ -102,7 +102,7 @@ namespace osu.Game.Skinning return songSelectComponents; case GlobalSkinComponentLookup.LookupType.MainHUDComponents: - var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => + var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs b/osu.Game/Skinning/DefaultSkinComponentsContainer.cs similarity index 61% rename from osu.Game/Skinning/SkinnableTargetComponentsContainer.cs rename to osu.Game/Skinning/DefaultSkinComponentsContainer.cs index 8c6726c3f4..0d6e6b6ce9 100644 --- a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs +++ b/osu.Game/Skinning/DefaultSkinComponentsContainer.cs @@ -2,39 +2,28 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; namespace osu.Game.Skinning { /// - /// A container which groups the components of a into a single object. - /// Optionally also applies a default layout to the components. + /// A container which can be used to specify default skin components layouts. + /// Handles applying a default layout to the components. /// - [Serializable] - public partial class SkinnableTargetComponentsContainer : Container, ISkinnableDrawable + public partial class DefaultSkinComponentsContainer : Container { - public bool IsEditable => false; - - public bool UsesFixedAnchor { get; set; } - private readonly Action? applyDefaults; /// /// Construct a wrapper with defaults that should be applied once. /// /// A function to apply the default layout. - public SkinnableTargetComponentsContainer(Action applyDefaults) - : this() - { - this.applyDefaults = applyDefaults; - } - - [JsonConstructor] - public SkinnableTargetComponentsContainer() + public DefaultSkinComponentsContainer(Action applyDefaults) { RelativeSizeAxes = Axes.Both; + + this.applyDefaults = applyDefaults; } protected override void LoadComplete() diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b2619fa55b..284a938bce 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -347,7 +347,7 @@ namespace osu.Game.Skinning switch (target.Lookup) { case GlobalSkinComponentLookup.LookupType.MainHUDComponents: - var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => + var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 37e98946c9..c55b4e789c 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -174,8 +175,9 @@ namespace osu.Game.Skinning foreach (var i in skinnableInfo) components.Add(i.CreateInstance()); - return new SkinnableTargetComponentsContainer + return new Container { + RelativeSizeAxes = Axes.Both, Children = components, }; } diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index df5299d427..b21e51c0cf 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -7,13 +7,14 @@ using System.Linq; using System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { public partial class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget { - private SkinnableTargetComponentsContainer? content; + private Container? content; public GlobalSkinComponentLookup.LookupType Target { get; } @@ -39,15 +40,16 @@ namespace osu.Game.Skinning foreach (var i in skinnableInfo) drawables.Add(i.CreateInstance()); - Reload(new SkinnableTargetComponentsContainer + Reload(new Container { + RelativeSizeAxes = Axes.Both, Children = drawables, }); } - public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as SkinnableTargetComponentsContainer); + public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as Container); - public void Reload(SkinnableTargetComponentsContainer? componentsContainer) + public void Reload(Container? componentsContainer) { ClearInternal(); components.Clear(); diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 62ef94691b..7de2b124e7 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -72,7 +72,7 @@ namespace osu.Game.Skinning switch (target.Lookup) { case GlobalSkinComponentLookup.LookupType.SongSelect: - var songSelectComponents = new SkinnableTargetComponentsContainer(_ => + var songSelectComponents = new DefaultSkinComponentsContainer(_ => { // do stuff when we need to. }); @@ -80,7 +80,7 @@ namespace osu.Game.Skinning return songSelectComponents; case GlobalSkinComponentLookup.LookupType.MainHUDComponents: - var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => + var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); From ca75f0ec7766b21cee79bb035e1cda9e845259fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 17:26:05 +0900 Subject: [PATCH 24/67] Apply NRT to `TestSceneBeatmapSkinFallbacks` --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 943f1d31ba..2109db81d4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Graphics.Containers; using osu.Framework.Lists; using osu.Framework.Testing; using osu.Framework.Timing; @@ -28,10 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene { - private ISkin currentBeatmapSkin; + private ISkin currentBeatmapSkin = null!; [Resolved] - private SkinManager skinManager { get; set; } + private SkinManager skinManager { get; set; } = null!; protected override bool HasCustomSteps => true; @@ -65,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay var actualInfo = actualComponentsContainer.CreateSkinnableInfo(); - var expectedComponentsContainer = (DefaultSkinComponentsContainer)expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)); + var expectedComponentsContainer = expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)) as Container; if (expectedComponentsContainer == null) return false; @@ -92,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay return almostEqual(actualInfo, expectedInfo); } - private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) => + private static bool almostEqual(SkinnableInfo info, SkinnableInfo? other) => other != null && info.Type == other.Type && info.Anchor == other.Anchor @@ -102,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay && Precision.AlmostEquals(info.Rotation, other.Rotation) && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual)); - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin); protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset(); @@ -111,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay { private readonly ISkin beatmapSkin; - public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin) + public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin) : base(beatmap, storyboard, referenceClock, audio) { this.beatmapSkin = beatmapSkin; From d9b4d932c97bec043d1fb069dd0d8a4091243d13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 17:47:34 +0900 Subject: [PATCH 25/67] Fix test container lookup failure --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 2109db81d4..94cb8517e9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -56,8 +56,8 @@ namespace osu.Game.Tests.Visual.Gameplay protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource) { - var actualComponentsContainer = Player.ChildrenOfType().First(s => s.Target == target) - .ChildrenOfType().SingleOrDefault(); + var targetContainer = Player.ChildrenOfType().First(s => s.Target == target); + var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer); if (actualComponentsContainer == null) return false; From 6010dde86edb4f70a71594f1c771c22bb6683aff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 15:30:29 +0900 Subject: [PATCH 26/67] Move `SkinnableInfo` to better namespace --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 1 - osu.Game/Extensions/DrawableExtensions.cs | 1 - osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs | 2 -- osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs | 1 - osu.Game/Skinning/ISkinnableTarget.cs | 2 +- osu.Game/Skinning/LegacyComboCounter.cs | 1 - osu.Game/{Screens/Play/HUD => Skinning}/SkinnableInfo.cs | 4 ++-- osu.Game/Skinning/SkinnableTargetContainer.cs | 2 +- 8 files changed, 4 insertions(+), 10 deletions(-) rename osu.Game/{Screens/Play/HUD => Skinning}/SkinnableInfo.cs (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 94cb8517e9..615bf609dc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -19,7 +19,6 @@ 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; diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 375960305c..021745e5e1 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; -using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 28ceaf09fc..3298c267f3 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -10,9 +10,7 @@ using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Localisation; using osu.Game.Screens.Edit.Components; -using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index c8f66f3e56..41b6b24da1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Extensions; using osu.Game.Screens.Edit; -using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; namespace osu.Game.Overlays.SkinEditor diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 3f116f8f76..6429d116dd 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -6,7 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Extensions; -using osu.Game.Screens.Play.HUD; +using osu.Game.Rulesets; namespace osu.Game.Skinning { diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs index c132a72001..1de13841fe 100644 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Skinning diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Skinning/SkinnableInfo.cs similarity index 98% rename from osu.Game/Screens/Play/HUD/SkinnableInfo.cs rename to osu.Game/Skinning/SkinnableInfo.cs index 9fdae50615..96aaed667e 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Skinning/SkinnableInfo.cs @@ -13,10 +13,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Extensions; -using osu.Game.Skinning; +using osu.Game.Rulesets; using osuTK; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Skinning { /// /// Serialised information governing custom changes to an . diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index b21e51c0cf..cbc719cef5 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -8,7 +8,7 @@ using System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning.Serialisation; namespace osu.Game.Skinning { From 9e651a7ca26f48eb9a95d770a29fb6bd680173f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 15:36:18 +0900 Subject: [PATCH 27/67] Rename `SkinnableInfo` to `SkinnableDrawableInfo` --- .../Skins/SkinDeserialisationTest.cs | 2 +- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 16 +++++++-------- osu.Game/Extensions/DrawableExtensions.cs | 20 +++++++++---------- .../SkinEditor/SkinComponentToolbox.cs | 2 +- .../SkinEditor/SkinEditorChangeHandler.cs | 4 ++-- osu.Game/Skinning/ISkinnableTarget.cs | 4 ++-- osu.Game/Skinning/LegacyComboCounter.cs | 2 +- ...nnableInfo.cs => SkinnableDrawableInfo.cs} | 8 ++++---- osu.Game/Skinning/SkinnableTargetContainer.cs | 2 +- 9 files changed, 30 insertions(+), 30 deletions(-) rename osu.Game/Skinning/{SkinnableInfo.cs => SkinnableDrawableInfo.cs} (93%) diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 3bab91bd9c..16cae1d34f 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Skins } } - var editableTypes = SkinnableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true); + var editableTypes = SkinnableDrawableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true); Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes)); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 615bf609dc..8318f70e8f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -90,15 +90,15 @@ namespace osu.Game.Tests.Visual.Gameplay return almostEqual(actualInfo, expectedInfo); } - private static bool almostEqual(SkinnableInfo info, SkinnableInfo? other) => + private static bool almostEqual(SkinnableDrawableInfo drawableInfo, SkinnableDrawableInfo? other) => other != null - && info.Type == other.Type - && info.Anchor == other.Anchor - && info.Origin == other.Origin - && Precision.AlmostEquals(info.Position, other.Position, 1) - && Precision.AlmostEquals(info.Scale, other.Scale) - && Precision.AlmostEquals(info.Rotation, other.Rotation) - && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual)); + && drawableInfo.Type == other.Type + && drawableInfo.Anchor == other.Anchor + && drawableInfo.Origin == other.Origin + && Precision.AlmostEquals(drawableInfo.Position, other.Position, 1) + && Precision.AlmostEquals(drawableInfo.Scale, other.Scale) + && Precision.AlmostEquals(drawableInfo.Rotation, other.Rotation) + && drawableInfo.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual)); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin); diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 021745e5e1..c590d4fa31 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -48,26 +48,26 @@ namespace osu.Game.Extensions public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) => drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta); - public static SkinnableInfo CreateSkinnableInfo(this Drawable component) => new SkinnableInfo(component); + public static SkinnableDrawableInfo CreateSkinnableInfo(this Drawable component) => new SkinnableDrawableInfo(component); - public static void ApplySkinnableInfo(this Drawable component, SkinnableInfo info) + public static void ApplySkinnableInfo(this Drawable component, SkinnableDrawableInfo drawableInfo) { // todo: can probably make this better via deserialisation directly using a common interface. - component.Position = info.Position; - component.Rotation = info.Rotation; - component.Scale = info.Scale; - component.Anchor = info.Anchor; - component.Origin = info.Origin; + component.Position = drawableInfo.Position; + component.Rotation = drawableInfo.Rotation; + component.Scale = drawableInfo.Scale; + component.Anchor = drawableInfo.Anchor; + component.Origin = drawableInfo.Origin; if (component is ISkinnableDrawable skinnable) { - skinnable.UsesFixedAnchor = info.UsesFixedAnchor; + skinnable.UsesFixedAnchor = drawableInfo.UsesFixedAnchor; foreach (var (_, property) in component.GetSettingsSourceProperties()) { var bindable = ((IBindable)property.GetValue(component)!); - if (!info.Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue)) + if (!drawableInfo.Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue)) { // TODO: We probably want to restore default if not included in serialisation information. // This is not simple to do as SetDefault() is only found in the typed Bindable interface right now. @@ -80,7 +80,7 @@ namespace osu.Game.Extensions if (component is Container container) { - foreach (var child in info.Children) + foreach (var child in drawableInfo.Children) container.Add(child.CreateInstance()); } } diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 3298c267f3..37bc7df4fa 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.SkinEditor { fill.Clear(); - var skinnableTypes = SkinnableInfo.GetAllAvailableDrawables(); + var skinnableTypes = SkinnableDrawableInfo.GetAllAvailableDrawables(target?.Ruleset); foreach (var type in skinnableTypes) attemptAddComponent(type); } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 41b6b24da1..2d519d3489 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -52,12 +52,12 @@ namespace osu.Game.Overlays.SkinEditor if (firstTarget == null) return; - var deserializedContent = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(newState)); + var deserializedContent = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(newState)); if (deserializedContent == null) return; - SkinnableInfo[] skinnableInfo = deserializedContent.ToArray(); + SkinnableDrawableInfo[] skinnableInfo = deserializedContent.ToArray(); Drawable[] targetComponents = firstTarget.Components.OfType().ToArray(); if (!skinnableInfo.Select(s => s.Type).SequenceEqual(targetComponents.Select(d => d.GetType()))) diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 6429d116dd..4610851216 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -29,7 +29,7 @@ namespace osu.Game.Skinning /// Serialise all children as . /// /// The serialised content. - IEnumerable CreateSkinnableInfo() => Components.Select(d => ((Drawable)d).CreateSkinnableInfo()); + IEnumerable CreateSkinnableInfo() => Components.Select(d => ((Drawable)d).CreateSkinnableInfo()); /// /// Reload this target from the current skin. @@ -39,7 +39,7 @@ namespace osu.Game.Skinning /// /// Reload this target from the provided skinnable information. /// - void Reload(SkinnableInfo[] skinnableInfo); + void Reload(SkinnableDrawableInfo[] skinnableInfo); /// /// Add a new skinnable component to this target. diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs index 1de13841fe..c03386266b 100644 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -44,7 +44,7 @@ namespace osu.Game.Skinning private readonly Container counterContainer; /// - /// Hides the combo counter internally without affecting its . + /// Hides the combo counter internally without affecting its . /// /// /// This is used for rulesets that provide their own combo counter and don't want this HUD one to be visible, diff --git a/osu.Game/Skinning/SkinnableInfo.cs b/osu.Game/Skinning/SkinnableDrawableInfo.cs similarity index 93% rename from osu.Game/Skinning/SkinnableInfo.cs rename to osu.Game/Skinning/SkinnableDrawableInfo.cs index 96aaed667e..5e30f94ac6 100644 --- a/osu.Game/Skinning/SkinnableInfo.cs +++ b/osu.Game/Skinning/SkinnableDrawableInfo.cs @@ -22,7 +22,7 @@ namespace osu.Game.Skinning /// Serialised information governing custom changes to an . /// [Serializable] - public class SkinnableInfo + public sealed class SkinnableDrawableInfo { public Type Type { get; set; } @@ -41,10 +41,10 @@ namespace osu.Game.Skinning public Dictionary Settings { get; set; } = new Dictionary(); - public List Children { get; } = new List(); + public List Children { get; } = new List(); [JsonConstructor] - public SkinnableInfo() + public SkinnableDrawableInfo() { } @@ -52,7 +52,7 @@ namespace osu.Game.Skinning /// Construct a new instance populating all attributes from the provided drawable. /// /// The drawable which attributes should be sourced from. - public SkinnableInfo(Drawable component) + public SkinnableDrawableInfo(Drawable component) { Type = component.GetType(); diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index cbc719cef5..586462cb8c 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Skinning Target = target; } - public void Reload(SkinnableInfo[] skinnableInfo) + public void Reload(SkinnableDrawableInfo[] skinnableInfo) { var drawables = new List(); From 856efd9fd9d04e312e77030d9740e738e1355b95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 15:47:41 +0900 Subject: [PATCH 28/67] Rename `SkinnableDrawableInfo` to `SerialisedDrawableInfo` --- osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 2 +- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 8 ++++---- osu.Game/Extensions/DrawableExtensions.cs | 4 ++-- .../Overlays/SkinEditor/SkinComponentToolbox.cs | 3 ++- .../Overlays/SkinEditor/SkinEditorChangeHandler.cs | 8 ++++---- osu.Game/Skinning/ISkinnableDrawable.cs | 6 ++++-- osu.Game/Skinning/ISkinnableTarget.cs | 7 +++---- osu.Game/Skinning/LegacyComboCounter.cs | 2 +- ...bleDrawableInfo.cs => SerialisedDrawableInfo.cs} | 13 ++++++------- osu.Game/Skinning/Skin.cs | 9 ++++----- osu.Game/Skinning/SkinnableTargetContainer.cs | 2 +- 11 files changed, 32 insertions(+), 32 deletions(-) rename osu.Game/Skinning/{SkinnableDrawableInfo.cs => SerialisedDrawableInfo.cs} (89%) diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 16cae1d34f..c782fbd084 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Skins } } - var editableTypes = SkinnableDrawableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true); + var editableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true); Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes)); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 8318f70e8f..5005cff265 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay if (actualComponentsContainer == null) return false; - var actualInfo = actualComponentsContainer.CreateSkinnableInfo(); + var actualInfo = actualComponentsContainer.CreateSerialisedInfo(); var expectedComponentsContainer = expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)) as Container; if (expectedComponentsContainer == null) @@ -84,13 +84,13 @@ namespace osu.Game.Tests.Visual.Gameplay Add(expectedComponentsAdjustmentContainer); expectedComponentsAdjustmentContainer.UpdateSubTree(); - var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo(); + var expectedInfo = expectedComponentsContainer.CreateSerialisedInfo(); Remove(expectedComponentsAdjustmentContainer, true); return almostEqual(actualInfo, expectedInfo); } - private static bool almostEqual(SkinnableDrawableInfo drawableInfo, SkinnableDrawableInfo? other) => + private static bool almostEqual(SerialisedDrawableInfo drawableInfo, SerialisedDrawableInfo? other) => other != null && drawableInfo.Type == other.Type && drawableInfo.Anchor == other.Anchor @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay && Precision.AlmostEquals(drawableInfo.Position, other.Position, 1) && Precision.AlmostEquals(drawableInfo.Scale, other.Scale) && Precision.AlmostEquals(drawableInfo.Rotation, other.Rotation) - && drawableInfo.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual)); + && drawableInfo.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual)); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin); diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index c590d4fa31..cc561cebc4 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -48,9 +48,9 @@ namespace osu.Game.Extensions public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) => drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta); - public static SkinnableDrawableInfo CreateSkinnableInfo(this Drawable component) => new SkinnableDrawableInfo(component); + public static SerialisedDrawableInfo CreateSerialisedInfo(this Drawable component) => new SerialisedDrawableInfo(component); - public static void ApplySkinnableInfo(this Drawable component, SkinnableDrawableInfo drawableInfo) + public static void ApplySerialisedInfo(this Drawable component, SerialisedDrawableInfo drawableInfo) { // todo: can probably make this better via deserialisation directly using a common interface. component.Position = drawableInfo.Position; diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 37bc7df4fa..2f3fd8b5e5 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -10,6 +10,7 @@ using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Screens.Edit.Components; using osu.Game.Skinning; using osuTK; @@ -48,7 +49,7 @@ namespace osu.Game.Overlays.SkinEditor { fill.Clear(); - var skinnableTypes = SkinnableDrawableInfo.GetAllAvailableDrawables(target?.Ruleset); + var skinnableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables(); foreach (var type in skinnableTypes) attemptAddComponent(type); } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 2d519d3489..77032589a0 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.SkinEditor if (firstTarget == null) return; - var skinnableInfos = firstTarget.CreateSkinnableInfo().ToArray(); + var skinnableInfos = firstTarget.CreateSerialisedInfo().ToArray(); string json = JsonConvert.SerializeObject(skinnableInfos, new JsonSerializerSettings { Formatting = Formatting.Indented }); stream.Write(Encoding.UTF8.GetBytes(json)); } @@ -52,12 +52,12 @@ namespace osu.Game.Overlays.SkinEditor if (firstTarget == null) return; - var deserializedContent = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(newState)); + var deserializedContent = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(newState)); if (deserializedContent == null) return; - SkinnableDrawableInfo[] skinnableInfo = deserializedContent.ToArray(); + SerialisedDrawableInfo[] skinnableInfo = deserializedContent.ToArray(); Drawable[] targetComponents = firstTarget.Components.OfType().ToArray(); if (!skinnableInfo.Select(s => s.Type).SequenceEqual(targetComponents.Select(d => d.GetType()))) @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.SkinEditor int i = 0; foreach (var drawable in targetComponents) - drawable.ApplySkinnableInfo(skinnableInfo[i++]); + drawable.ApplySerialisedInfo(skinnableInfo[i++]); } } } diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISkinnableDrawable.cs index 1ecd6f967e..5ec2fa0f43 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -13,8 +13,10 @@ namespace osu.Game.Skinning /// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications. /// /// - /// Attaching this interface to any will make it serialisable to skin settings. - /// Adding annotated bindables will also serialise these settings alongside each instance. + /// Attaching this interface to any will make it serialisable to user skins (see ). + /// Adding annotated bindables will also allow serialising settings automatically. + /// + /// Serialisation is done via using . /// public interface ISkinnableDrawable : IDrawable { diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 4610851216..e10fc6e069 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Extensions; -using osu.Game.Rulesets; namespace osu.Game.Skinning { @@ -26,10 +25,10 @@ namespace osu.Game.Skinning IBindableList Components { get; } /// - /// Serialise all children as . + /// Serialise all children as . /// /// The serialised content. - IEnumerable CreateSkinnableInfo() => Components.Select(d => ((Drawable)d).CreateSkinnableInfo()); + IEnumerable CreateSerialisedInfo() => Components.Select(d => ((Drawable)d).CreateSerialisedInfo()); /// /// Reload this target from the current skin. @@ -39,7 +38,7 @@ namespace osu.Game.Skinning /// /// Reload this target from the provided skinnable information. /// - void Reload(SkinnableDrawableInfo[] skinnableInfo); + void Reload(SerialisedDrawableInfo[] skinnableInfo); /// /// Add a new skinnable component to this target. diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs index c03386266b..f46f5a69f0 100644 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -44,7 +44,7 @@ namespace osu.Game.Skinning private readonly Container counterContainer; /// - /// Hides the combo counter internally without affecting its . + /// Hides the combo counter internally without affecting its . /// /// /// This is used for rulesets that provide their own combo counter and don't want this HUD one to be visible, diff --git a/osu.Game/Skinning/SkinnableDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs similarity index 89% rename from osu.Game/Skinning/SkinnableDrawableInfo.cs rename to osu.Game/Skinning/SerialisedDrawableInfo.cs index 5e30f94ac6..8bcfed1dbc 100644 --- a/osu.Game/Skinning/SkinnableDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Extensions; -using osu.Game.Rulesets; using osuTK; namespace osu.Game.Skinning @@ -22,7 +21,7 @@ namespace osu.Game.Skinning /// Serialised information governing custom changes to an . /// [Serializable] - public sealed class SkinnableDrawableInfo + public sealed class SerialisedDrawableInfo { public Type Type { get; set; } @@ -41,10 +40,10 @@ namespace osu.Game.Skinning public Dictionary Settings { get; set; } = new Dictionary(); - public List Children { get; } = new List(); + public List Children { get; } = new List(); [JsonConstructor] - public SkinnableDrawableInfo() + public SerialisedDrawableInfo() { } @@ -52,7 +51,7 @@ namespace osu.Game.Skinning /// Construct a new instance populating all attributes from the provided drawable. /// /// The drawable which attributes should be sourced from. - public SkinnableDrawableInfo(Drawable component) + public SerialisedDrawableInfo(Drawable component) { Type = component.GetType(); @@ -75,7 +74,7 @@ namespace osu.Game.Skinning if (component is Container container) { foreach (var child in container.OfType().OfType()) - Children.Add(child.CreateSkinnableInfo()); + Children.Add(child.CreateSerialisedInfo()); } } @@ -88,7 +87,7 @@ namespace osu.Game.Skinning try { Drawable d = (Drawable)Activator.CreateInstance(Type)!; - d.ApplySkinnableInfo(this); + d.ApplySerialisedInfo(this); return d; } catch (Exception e) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index c55b4e789c..44bdcafe4c 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -18,7 +18,6 @@ using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; -using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { @@ -38,9 +37,9 @@ namespace osu.Game.Skinning public SkinConfiguration Configuration { get; set; } - public IDictionary DrawableComponentInfo => drawableComponentInfo; + public IDictionary DrawableComponentInfo => drawableComponentInfo; - private readonly Dictionary drawableComponentInfo = new Dictionary(); + private readonly Dictionary drawableComponentInfo = new Dictionary(); public abstract ISample? GetSample(ISampleInfo sampleInfo); @@ -120,7 +119,7 @@ namespace osu.Game.Skinning jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); - var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); + var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); if (deserializedContent == null) continue; @@ -155,7 +154,7 @@ namespace osu.Game.Skinning /// The target container to serialise to this skin. public void UpdateDrawableTarget(ISkinnableTarget targetContainer) { - DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray(); + DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSerialisedInfo().ToArray(); } public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup) diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index 586462cb8c..8b37767113 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Skinning Target = target; } - public void Reload(SkinnableDrawableInfo[] skinnableInfo) + public void Reload(SerialisedDrawableInfo[] skinnableInfo) { var drawables = new List(); From 8cb5a51aa77d6db5cce44273b7722543d38619a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 15:14:38 +0900 Subject: [PATCH 29/67] Add further documentation to skin classes --- osu.Game/Skinning/ISkin.cs | 2 +- osu.Game/Skinning/ISkinComponentLookup.cs | 3 ++- osu.Game/Skinning/ISkinSource.cs | 10 +++++++++- osu.Game/Skinning/ISkinnableDrawable.cs | 4 ++-- osu.Game/Skinning/SerialisedDrawableInfo.cs | 3 ++- osu.Game/Skinning/SkinProvidingContainer.cs | 7 ++++++- osu.Game/Skinning/SkinReloadableDrawable.cs | 3 ++- osu.Game/Skinning/SkinTransformer.cs | 7 +++++++ 8 files changed, 31 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 45be5582f6..fa04dda202 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -10,7 +10,7 @@ using osu.Game.Audio; namespace osu.Game.Skinning { /// - /// Provides access to skinnable elements. + /// Provides access to various elements contained by a skin. /// public interface ISkin { diff --git a/osu.Game/Skinning/ISkinComponentLookup.cs b/osu.Game/Skinning/ISkinComponentLookup.cs index be4043d7cf..25ee086707 100644 --- a/osu.Game/Skinning/ISkinComponentLookup.cs +++ b/osu.Game/Skinning/ISkinComponentLookup.cs @@ -4,7 +4,8 @@ namespace osu.Game.Skinning { /// - /// A lookup type which can be used with . + /// The base lookup type to be used with . + /// Should be implemented as necessary to add further criteria to lookups, which are usually consumed by ruleset transformers or legacy lookup cases. /// /// /// Implementations of should match on types implementing this interface diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index 89f656a12c..b05d52d47c 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -7,8 +7,16 @@ using System.Collections.Generic; namespace osu.Game.Skinning { /// - /// Provides access to skinnable elements. + /// An abstract skin implementation which generally provides access to more than one skins (with fallback logic). /// + /// + /// Common usage is to do an initial lookup via , and use the returned + /// to do further lookups for related components. + /// + /// The initial lookup is used to lock consecutive lookups to the same underlying skin source (as to not get some elements + /// from one skin and others from another, which would be the case if using methods like + /// directly). + /// public interface ISkinSource : ISkin { /// diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISkinnableDrawable.cs index 5ec2fa0f43..48f5b91c29 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -10,13 +10,13 @@ using osu.Game.Configuration; namespace osu.Game.Skinning { /// - /// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications. + /// A drawable which can be serialised to a skin, placed and customised via the skin layout editor. /// /// /// Attaching this interface to any will make it serialisable to user skins (see ). /// Adding annotated bindables will also allow serialising settings automatically. /// - /// Serialisation is done via using . + /// Serialisation is done via using . /// public interface ISkinnableDrawable : IDrawable { diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index 8bcfed1dbc..90762da011 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -18,7 +18,8 @@ using osuTK; namespace osu.Game.Skinning { /// - /// Serialised information governing custom changes to an . + /// Serialised backing data for s. + /// Used for json serialisation in user skins. /// [Serializable] public sealed class SerialisedDrawableInfo diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index afead7b072..2612e0b47c 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -15,8 +15,13 @@ using osu.Game.Audio; namespace osu.Game.Skinning { /// - /// A container which adds a local to the hierarchy. + /// A container which adds a provided to the DI skin lookup hierarchy. /// + /// + /// This container will expose an to its children. + /// The source will first consider the skin provided via the constructor (if any), then fallback + /// to any providers in the parent DI hierarchy. + /// public partial class SkinProvidingContainer : Container, ISkinSource { public event Action? SourceChanged; diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 1c947a4a84..cef1db4bc0 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -9,7 +9,8 @@ using osu.Framework.Graphics.Pooling; namespace osu.Game.Skinning { /// - /// A drawable which has a callback when the skin changes. + /// A poolable drawable implementation which has a pre-wired callback (see ) that fires + /// once on load and again on any subsequent skin change. /// public abstract partial class SkinReloadableDrawable : PoolableDrawable { diff --git a/osu.Game/Skinning/SkinTransformer.cs b/osu.Game/Skinning/SkinTransformer.cs index e05961d404..ed5b04da1e 100644 --- a/osu.Game/Skinning/SkinTransformer.cs +++ b/osu.Game/Skinning/SkinTransformer.cs @@ -10,6 +10,13 @@ using osu.Game.Audio; namespace osu.Game.Skinning { + /// + /// A default skin transformer, which falls back to the provided skin by default. + /// + /// + /// Implementations of skin transformers should generally derive this class and override + /// individual lookup methods, modifying the lookup flow as required. + /// public abstract class SkinTransformer : ISkinTransformer { public ISkin Skin { get; } From d159d6b9700d90f6a40cda0f832df59f6086e7ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 16:01:26 +0900 Subject: [PATCH 30/67] Rename `ISkinnableDrawable` to `ISerialisableDrawable` --- .../Skins/SkinDeserialisationTest.cs | 2 +- osu.Game/Extensions/DrawableExtensions.cs | 2 +- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 4 ++-- .../SkinEditor/SkinBlueprintContainer.cs | 22 +++++++++---------- .../SkinEditor/SkinComponentToolbox.cs | 2 +- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 10 ++++----- .../SkinEditor/SkinEditorChangeHandler.cs | 4 ++-- .../SkinEditor/SkinSelectionHandler.cs | 10 ++++----- osu.Game/Screens/Play/HUD/BPMCounter.cs | 2 +- .../ClicksPerSecond/ClicksPerSecondCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ComboCounter.cs | 2 +- .../Play/HUD/DefaultAccuracyCounter.cs | 2 +- .../Screens/Play/HUD/DefaultHealthDisplay.cs | 2 +- .../Screens/Play/HUD/DefaultScoreCounter.cs | 2 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 2 +- .../JudgementCounterDisplay.cs | 2 +- .../Play/HUD/PerformancePointsCounter.cs | 2 +- osu.Game/Screens/Play/HUD/SongProgress.cs | 2 +- .../Screens/Play/HUD/UnstableRateCounter.cs | 2 +- osu.Game/Skinning/Components/BigBlackBox.cs | 2 +- .../Skinning/FontAdjustableSkinComponent.cs | 2 +- ...leDrawable.cs => ISerialisableDrawable.cs} | 6 ++--- osu.Game/Skinning/ISkinnableTarget.cs | 8 +++---- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacyComboCounter.cs | 2 +- osu.Game/Skinning/LegacyHealthDisplay.cs | 2 +- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- osu.Game/Skinning/SerialisedDrawableInfo.cs | 14 +++++++----- osu.Game/Skinning/SkinnableSprite.cs | 2 +- osu.Game/Skinning/SkinnableTargetContainer.cs | 10 ++++----- 30 files changed, 67 insertions(+), 63 deletions(-) rename osu.Game/Skinning/{ISkinnableDrawable.cs => ISerialisableDrawable.cs} (87%) diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index c782fbd084..b897523560 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Skins } } - var editableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true); + var editableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISerialisableDrawable)?.IsEditable == true); Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes)); } diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index cc561cebc4..b6f74f5777 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -59,7 +59,7 @@ namespace osu.Game.Extensions component.Anchor = drawableInfo.Anchor; component.Origin = drawableInfo.Origin; - if (component is ISkinnableDrawable skinnable) + if (component is ISerialisableDrawable skinnable) { skinnable.UsesFixedAnchor = drawableInfo.UsesFixedAnchor; diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 893bc4bac2..55ea362873 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -17,7 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.SkinEditor { - public partial class SkinBlueprint : SelectionBlueprint + public partial class SkinBlueprint : SelectionBlueprint { private Container box = null!; @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private OsuColour colours { get; set; } = null!; - public SkinBlueprint(ISkinnableDrawable component) + public SkinBlueprint(ISerialisableDrawable component) : base(component) { } diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs index a448b3d0c1..1e7bec5417 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs @@ -24,11 +24,11 @@ using osuTK.Input; namespace osu.Game.Overlays.SkinEditor { - public partial class SkinBlueprintContainer : BlueprintContainer + public partial class SkinBlueprintContainer : BlueprintContainer { private readonly Drawable target; - private readonly List> targetComponents = new List>(); + private readonly List> targetComponents = new List>(); [Resolved] private SkinEditor editor { get; set; } = null!; @@ -55,7 +55,7 @@ namespace osu.Game.Overlays.SkinEditor foreach (var targetContainer in targetContainers) { - var bindableList = new BindableList { BindTarget = targetContainer.Components }; + var bindableList = new BindableList { BindTarget = targetContainer.Components }; bindableList.BindCollectionChanged(componentsChanged, true); targetComponents.Add(bindableList); @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.SkinEditor case NotifyCollectionChangedAction.Add: Debug.Assert(e.NewItems != null); - foreach (var item in e.NewItems.Cast()) + foreach (var item in e.NewItems.Cast()) AddBlueprintFor(item); break; @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.SkinEditor case NotifyCollectionChangedAction.Reset: Debug.Assert(e.OldItems != null); - foreach (var item in e.OldItems.Cast()) + foreach (var item in e.OldItems.Cast()) RemoveBlueprintFor(item); break; @@ -85,16 +85,16 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(e.NewItems != null); Debug.Assert(e.OldItems != null); - foreach (var item in e.OldItems.Cast()) + foreach (var item in e.OldItems.Cast()) RemoveBlueprintFor(item); - foreach (var item in e.NewItems.Cast()) + foreach (var item in e.NewItems.Cast()) AddBlueprintFor(item); break; } }); - protected override void AddBlueprintFor(ISkinnableDrawable item) + protected override void AddBlueprintFor(ISerialisableDrawable item) { if (!item.IsEditable) return; @@ -145,12 +145,12 @@ namespace osu.Game.Overlays.SkinEditor // convert to game space coordinates delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); - SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, delta)); + SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, delta)); } - protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(ISkinnableDrawable component) + protected override SelectionBlueprint CreateBlueprintFor(ISerialisableDrawable component) => new SkinBlueprint(component); protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 2f3fd8b5e5..cd9f7cc935 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.SkinEditor { Drawable instance = (Drawable)Activator.CreateInstance(type)!; - if (!((ISkinnableDrawable)instance).IsEditable) return; + if (!((ISerialisableDrawable)instance).IsEditable) return; fill.Add(new ToolboxComponentButton(instance, target) { diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 866de7e621..aafc97d769 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.SkinEditor public const float MENU_HEIGHT = 40; - public readonly BindableList SelectedComponents = new BindableList(); + public readonly BindableList SelectedComponents = new BindableList(); protected override bool StartHidden => true; @@ -302,13 +302,13 @@ namespace osu.Game.Overlays.SkinEditor private void placeComponent(Type type) { - if (!(Activator.CreateInstance(type) is ISkinnableDrawable component)) - throw new InvalidOperationException($"Attempted to instantiate a component for placement which was not an {typeof(ISkinnableDrawable)}."); + if (!(Activator.CreateInstance(type) is ISerialisableDrawable component)) + throw new InvalidOperationException($"Attempted to instantiate a component for placement which was not an {typeof(ISerialisableDrawable)}."); placeComponent(component); } - private void placeComponent(ISkinnableDrawable component, bool applyDefaults = true) + private void placeComponent(ISerialisableDrawable component, bool applyDefaults = true) { var targetContainer = getFirstTarget(); @@ -400,7 +400,7 @@ namespace osu.Game.Overlays.SkinEditor this.FadeOut(TRANSITION_DURATION, Easing.OutQuint); } - public void DeleteItems(ISkinnableDrawable[] items) + public void DeleteItems(ISerialisableDrawable[] items) { foreach (var item in items) availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 77032589a0..65872920f1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.SkinEditor private readonly ISkinnableTarget? firstTarget; // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable - private readonly BindableList? components; + private readonly BindableList? components; public SkinEditorChangeHandler(Drawable targetScreen) { @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.SkinEditor if (firstTarget == null) return; - components = new BindableList { BindTarget = firstTarget.Components }; + components = new BindableList { BindTarget = firstTarget.Components }; components.BindCollectionChanged((_, _) => SaveState()); } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 86fcd35e03..c628ad8480 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Overlays.SkinEditor { - public partial class SkinSelectionHandler : SelectionHandler + public partial class SkinSelectionHandler : SelectionHandler { [Resolved] private SkinEditor skinEditor { get; set; } = null!; @@ -147,7 +147,7 @@ namespace osu.Game.Overlays.SkinEditor return true; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { foreach (var c in SelectedBlueprints) { @@ -178,10 +178,10 @@ namespace osu.Game.Overlays.SkinEditor SelectionBox.CanReverse = false; } - protected override void DeleteItems(IEnumerable items) => + protected override void DeleteItems(IEnumerable items) => skinEditor.DeleteItems(items.ToArray()); - protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors()) { @@ -209,7 +209,7 @@ namespace osu.Game.Overlays.SkinEditor foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) + IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) { var displayableAnchors = new[] { diff --git a/osu.Game/Screens/Play/HUD/BPMCounter.cs b/osu.Game/Screens/Play/HUD/BPMCounter.cs index 500ab0169f..cd24237493 100644 --- a/osu.Game/Screens/Play/HUD/BPMCounter.cs +++ b/osu.Game/Screens/Play/HUD/BPMCounter.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { - public partial class BPMCounter : RollingCounter, ISkinnableDrawable + public partial class BPMCounter : RollingCounter, ISerialisableDrawable { protected override double RollingDuration => 750; diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index cb72bb5f6f..1aa7c5e091 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public partial class ClicksPerSecondCounter : RollingCounter, ISkinnableDrawable + public partial class ClicksPerSecondCounter : RollingCounter, ISerialisableDrawable { [Resolved] private ClicksPerSecondCalculator calculator { get; set; } = null!; diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index afccbc4ef0..17531281aa 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -7,7 +7,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public abstract partial class ComboCounter : RollingCounter, ISkinnableDrawable + public abstract partial class ComboCounter : RollingCounter, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index 1a082e58b7..eb3c71afbb 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -9,7 +9,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public partial class DefaultAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable + public partial class DefaultAccuracyCounter : GameplayAccuracyCounter, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index 62d66efb33..2c43905a46 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -19,7 +19,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public partial class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISkinnableDrawable + public partial class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISerialisableDrawable { /// /// The base opacity of the glow. diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index f116617271..7cc2dc1751 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -10,7 +10,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public partial class DefaultScoreCounter : GameplayScoreCounter, ISkinnableDrawable + public partial class DefaultScoreCounter : GameplayScoreCounter, ISerialisableDrawable { public DefaultScoreCounter() { diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 191f63e97d..5d65208afe 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { - public abstract partial class HitErrorMeter : CompositeDrawable, ISkinnableDrawable + public abstract partial class HitErrorMeter : CompositeDrawable, ISerialisableDrawable { protected HitWindows HitWindows { get; private set; } diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs index 80d2e0863f..a9b59a02b5 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD.JudgementCounter { - public partial class JudgementCounterDisplay : CompositeDrawable, ISkinnableDrawable + public partial class JudgementCounterDisplay : CompositeDrawable, ISerialisableDrawable { public const int TRANSFORM_DURATION = 250; diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 15484f2965..4f37c215e9 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -35,7 +35,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { - public partial class PerformancePointsCounter : RollingCounter, ISkinnableDrawable + public partial class PerformancePointsCounter : RollingCounter, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 4647c0352b..ebe2fb83e6 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -14,7 +14,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public abstract partial class SongProgress : OverlayContainer, ISkinnableDrawable + public abstract partial class SongProgress : OverlayContainer, ISerialisableDrawable { // Some implementations of this element allow seeking during gameplay playback. // Set a sane default of never handling input to override the behaviour provided by OverlayContainer. diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index f450ae799e..4ceca817e2 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -20,7 +20,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { - public partial class UnstableRateCounter : RollingCounter, ISkinnableDrawable + public partial class UnstableRateCounter : RollingCounter, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs index 043a276e49..3c63dae8d8 100644 --- a/osu.Game/Skinning/Components/BigBlackBox.cs +++ b/osu.Game/Skinning/Components/BigBlackBox.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning.Components /// Intended to be a test bed for skinning. May be removed at some point in the future. /// [UsedImplicitly] - public partial class BigBlackBox : CompositeDrawable, ISkinnableDrawable + public partial class BigBlackBox : CompositeDrawable, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/FontAdjustableSkinComponent.cs b/osu.Game/Skinning/FontAdjustableSkinComponent.cs index 2e41b35abb..8f3a1d41c6 100644 --- a/osu.Game/Skinning/FontAdjustableSkinComponent.cs +++ b/osu.Game/Skinning/FontAdjustableSkinComponent.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning /// /// A skin component that contains text and allows the user to choose its font. /// - public abstract partial class FontAdjustableSkinComponent : Container, ISkinnableDrawable + public abstract partial class FontAdjustableSkinComponent : Container, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs similarity index 87% rename from osu.Game/Skinning/ISkinnableDrawable.cs rename to osu.Game/Skinning/ISerialisableDrawable.cs index 48f5b91c29..23cbda6344 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISerialisableDrawable.cs @@ -18,7 +18,7 @@ namespace osu.Game.Skinning /// /// Serialisation is done via using . /// - public interface ISkinnableDrawable : IDrawable + public interface ISerialisableDrawable : IDrawable { /// /// Whether this component should be editable by an end user. @@ -26,8 +26,8 @@ namespace osu.Game.Skinning bool IsEditable => true; /// - /// In the context of the skin layout editor, whether this has a permanent anchor defined. - /// If , this 's is automatically determined by proximity, + /// In the context of the skin layout editor, whether this has a permanent anchor defined. + /// If , this 's is automatically determined by proximity, /// If , a fixed anchor point has been defined. /// bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index e10fc6e069..5833898d81 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -10,7 +10,7 @@ using osu.Game.Extensions; namespace osu.Game.Skinning { /// - /// Denotes a container which can house s. + /// Denotes a container which can house s. /// public interface ISkinnableTarget : IDrawable { @@ -22,7 +22,7 @@ namespace osu.Game.Skinning /// /// A bindable list of components which are being tracked by this skinnable target. /// - IBindableList Components { get; } + IBindableList Components { get; } /// /// Serialise all children as . @@ -44,12 +44,12 @@ namespace osu.Game.Skinning /// Add a new skinnable component to this target. /// /// The component to add. - void Add(ISkinnableDrawable drawable); + void Add(ISerialisableDrawable drawable); /// /// Remove an existing skinnable component from this target. /// /// The component to remove. - void Remove(ISkinnableDrawable component); + void Remove(ISerialisableDrawable component); } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index e75fb1e7e9..c99cdba91c 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -8,7 +8,7 @@ using osuTK; namespace osu.Game.Skinning { - public partial class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable + public partial class LegacyAccuracyCounter : GameplayAccuracyCounter, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs index f46f5a69f0..cd72055fce 100644 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -14,7 +14,7 @@ namespace osu.Game.Skinning /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public partial class LegacyComboCounter : CompositeDrawable, ISkinnableDrawable + public partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable { public Bindable Current { get; } = new BindableInt { MinValue = 0 }; diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index c3cb9770fa..f785022f84 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Skinning { - public partial class LegacyHealthDisplay : HealthDisplay, ISkinnableDrawable + public partial class LegacyHealthDisplay : HealthDisplay, ISerialisableDrawable { private const double epic_cutoff = 0.5; diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 88e7bbc23a..d8ee6b21de 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -8,7 +8,7 @@ using osuTK; namespace osu.Game.Skinning { - public partial class LegacyScoreCounter : GameplayScoreCounter, ISkinnableDrawable + public partial class LegacyScoreCounter : GameplayScoreCounter, ISerialisableDrawable { protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index 90762da011..f571f1a945 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -18,9 +18,13 @@ using osuTK; namespace osu.Game.Skinning { /// - /// Serialised backing data for s. + /// Serialised backing data for s. /// Used for json serialisation in user skins. /// + /// + /// Can be created using . + /// Can also be applied to an existing drawable using . + /// [Serializable] public sealed class SerialisedDrawableInfo { @@ -36,7 +40,7 @@ namespace osu.Game.Skinning public Anchor Origin { get; set; } - /// + /// public bool UsesFixedAnchor { get; set; } public Dictionary Settings { get; set; } = new Dictionary(); @@ -62,7 +66,7 @@ namespace osu.Game.Skinning Anchor = component.Anchor; Origin = component.Origin; - if (component is ISkinnableDrawable skinnable) + if (component is ISerialisableDrawable skinnable) UsesFixedAnchor = skinnable.UsesFixedAnchor; foreach (var (_, property) in component.GetSettingsSourceProperties()) @@ -74,7 +78,7 @@ namespace osu.Game.Skinning if (component is Container container) { - foreach (var child in container.OfType().OfType()) + foreach (var child in container.OfType().OfType()) Children.Add(child.CreateSerialisedInfo()); } } @@ -102,7 +106,7 @@ namespace osu.Game.Skinning { return typeof(OsuGame).Assembly.GetTypes() .Where(t => !t.IsInterface && !t.IsAbstract) - .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) + .Where(t => typeof(ISerialisableDrawable).IsAssignableFrom(t)) .OrderBy(t => t.Name) .ToArray(); } diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index c3449562c3..1d97566470 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -21,7 +21,7 @@ namespace osu.Game.Skinning /// /// A skinnable element which uses a single texture backing. /// - public partial class SkinnableSprite : SkinnableDrawable, ISkinnableDrawable + public partial class SkinnableSprite : SkinnableDrawable, ISerialisableDrawable { protected override bool ApplySizeRestrictionsToDefault => true; diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index 8b37767113..7acf83576a 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -18,9 +18,9 @@ namespace osu.Game.Skinning public GlobalSkinComponentLookup.LookupType Target { get; } - public IBindableList Components => components; + public IBindableList Components => components; - private readonly BindableList components = new BindableList(); + private readonly BindableList components = new BindableList(); public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; // ensure that components are loaded even if the target container is hidden (ie. due to user toggle). @@ -68,7 +68,7 @@ namespace osu.Game.Skinning LoadComponentAsync(content, wrapper => { AddInternal(wrapper); - components.AddRange(wrapper.Children.OfType()); + components.AddRange(wrapper.Children.OfType()); ComponentsLoaded = true; }, (cancellationSource = new CancellationTokenSource()).Token); } @@ -79,7 +79,7 @@ namespace osu.Game.Skinning /// /// Thrown when attempting to add an element to a target which is not supported by the current skin. /// Thrown if the provided instance is not a . - public void Add(ISkinnableDrawable component) + public void Add(ISerialisableDrawable component) { if (content == null) throw new NotSupportedException("Attempting to add a new component to a target container which is not supported by the current skin."); @@ -94,7 +94,7 @@ namespace osu.Game.Skinning /// /// Thrown when attempting to add an element to a target which is not supported by the current skin. /// Thrown if the provided instance is not a . - public void Remove(ISkinnableDrawable component) + public void Remove(ISerialisableDrawable component) { if (content == null) throw new NotSupportedException("Attempting to remove a new component from a target container which is not supported by the current skin."); From a7b47f6503e5de4cbaa1674cfa559e7e4e71247d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 16:28:42 +0900 Subject: [PATCH 31/67] Rename `ISkinnableTarget` to `ISerialisableDrawableContainer` --- osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs | 2 +- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 10 +++++----- .../Overlays/SkinEditor/SkinEditorChangeHandler.cs | 4 ++-- ...ableTarget.cs => ISerialisableDrawableContainer.cs} | 5 +++-- osu.Game/Skinning/Skin.cs | 4 ++-- osu.Game/Skinning/SkinnableTargetContainer.cs | 6 +++--- 6 files changed, 16 insertions(+), 15 deletions(-) rename osu.Game/Skinning/{ISkinnableTarget.cs => ISerialisableDrawableContainer.cs} (86%) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs index 1e7bec5417..08b4c85e4a 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.SkinEditor SelectedItems.BindTo(editor.SelectedComponents); // track each target container on the current screen. - var targetContainers = target.ChildrenOfType().ToArray(); + var targetContainers = target.ChildrenOfType().ToArray(); if (targetContainers.Length == 0) { diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index aafc97d769..39187d6471 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -339,18 +339,18 @@ namespace osu.Game.Overlays.SkinEditor settingsSidebar.Add(new SkinSettingsToolbox(component)); } - private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private IEnumerable availableTargets => targetScreen.ChildrenOfType(); - private ISkinnableTarget? getFirstTarget() => availableTargets.FirstOrDefault(); + private ISerialisableDrawableContainer? getFirstTarget() => availableTargets.FirstOrDefault(); - private ISkinnableTarget? getTarget(GlobalSkinComponentLookup.LookupType target) + private ISerialisableDrawableContainer? getTarget(GlobalSkinComponentLookup.LookupType target) { return availableTargets.FirstOrDefault(c => c.Target == target); } private void revert() { - ISkinnableTarget[] targetContainers = availableTargets.ToArray(); + ISerialisableDrawableContainer[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) { @@ -370,7 +370,7 @@ namespace osu.Game.Overlays.SkinEditor if (!hasBegunMutating) return; - ISkinnableTarget[] targetContainers = availableTargets.ToArray(); + ISerialisableDrawableContainer[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) currentSkin.Value.UpdateDrawableTarget(t); diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 65872920f1..6c66714b79 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.SkinEditor { public partial class SkinEditorChangeHandler : EditorChangeHandler { - private readonly ISkinnableTarget? firstTarget; + private readonly ISerialisableDrawableContainer? firstTarget; // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable private readonly BindableList? components; @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.SkinEditor // In the future we'll want this to cover all changes, even to skin's `InstantiationInfo`. // We'll also need to consider cases where multiple targets are on screen at the same time. - firstTarget = targetScreen.ChildrenOfType().FirstOrDefault(); + firstTarget = targetScreen.ChildrenOfType().FirstOrDefault(); if (firstTarget == null) return; diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISerialisableDrawableContainer.cs similarity index 86% rename from osu.Game/Skinning/ISkinnableTarget.cs rename to osu.Game/Skinning/ISerialisableDrawableContainer.cs index 5833898d81..386ebee085 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISerialisableDrawableContainer.cs @@ -10,9 +10,10 @@ using osu.Game.Extensions; namespace osu.Game.Skinning { /// - /// Denotes a container which can house s. + /// A container which can house s. + /// Contains functionality for new drawables to be added, removed, and reloaded from provided . /// - public interface ISkinnableTarget : IDrawable + public interface ISerialisableDrawableContainer : IDrawable { /// /// The definition of this target. diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 44bdcafe4c..ebe9d2bdd3 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -143,7 +143,7 @@ namespace osu.Game.Skinning /// Remove all stored customisations for the provided target. /// /// The target container to reset. - public void ResetDrawableTarget(ISkinnableTarget targetContainer) + public void ResetDrawableTarget(ISerialisableDrawableContainer targetContainer) { DrawableComponentInfo.Remove(targetContainer.Target); } @@ -152,7 +152,7 @@ namespace osu.Game.Skinning /// Update serialised information for the provided target. /// /// The target container to serialise to this skin. - public void UpdateDrawableTarget(ISkinnableTarget targetContainer) + public void UpdateDrawableTarget(ISerialisableDrawableContainer targetContainer) { DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSerialisedInfo().ToArray(); } diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index 7acf83576a..abed2c09b6 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -12,7 +12,7 @@ using osu.Game.Skinning.Serialisation; namespace osu.Game.Skinning { - public partial class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget + public partial class SkinnableTargetContainer : SkinReloadableDrawable, ISerialisableDrawableContainer { private Container? content; @@ -76,7 +76,7 @@ namespace osu.Game.Skinning ComponentsLoaded = true; } - /// + /// /// Thrown when attempting to add an element to a target which is not supported by the current skin. /// Thrown if the provided instance is not a . public void Add(ISerialisableDrawable component) @@ -91,7 +91,7 @@ namespace osu.Game.Skinning components.Add(component); } - /// + /// /// Thrown when attempting to add an element to a target which is not supported by the current skin. /// Thrown if the provided instance is not a . public void Remove(ISerialisableDrawable component) From e61d2d571c1b23c6f1e0d4df9f35fdc2f5b93053 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 16:35:23 +0900 Subject: [PATCH 32/67] Move the lookup type out of `ISserialisableDrawableContainer` --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 6 +++--- osu.Game/Skinning/ISerialisableDrawableContainer.cs | 5 ----- osu.Game/Skinning/Skin.cs | 6 +++--- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 39187d6471..25448906b2 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -339,7 +339,7 @@ namespace osu.Game.Overlays.SkinEditor settingsSidebar.Add(new SkinSettingsToolbox(component)); } - private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private IEnumerable availableTargets => targetScreen.ChildrenOfType(); private ISerialisableDrawableContainer? getFirstTarget() => availableTargets.FirstOrDefault(); @@ -350,7 +350,7 @@ namespace osu.Game.Overlays.SkinEditor private void revert() { - ISerialisableDrawableContainer[] targetContainers = availableTargets.ToArray(); + SkinnableTargetContainer[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) { @@ -370,7 +370,7 @@ namespace osu.Game.Overlays.SkinEditor if (!hasBegunMutating) return; - ISerialisableDrawableContainer[] targetContainers = availableTargets.ToArray(); + SkinnableTargetContainer[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) currentSkin.Value.UpdateDrawableTarget(t); diff --git a/osu.Game/Skinning/ISerialisableDrawableContainer.cs b/osu.Game/Skinning/ISerialisableDrawableContainer.cs index 386ebee085..54d8a68c95 100644 --- a/osu.Game/Skinning/ISerialisableDrawableContainer.cs +++ b/osu.Game/Skinning/ISerialisableDrawableContainer.cs @@ -15,11 +15,6 @@ namespace osu.Game.Skinning /// public interface ISerialisableDrawableContainer : IDrawable { - /// - /// The definition of this target. - /// - GlobalSkinComponentLookup.LookupType Target { get; } - /// /// A bindable list of components which are being tracked by this skinnable target. /// diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index ebe9d2bdd3..b97b167419 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -143,7 +143,7 @@ namespace osu.Game.Skinning /// Remove all stored customisations for the provided target. /// /// The target container to reset. - public void ResetDrawableTarget(ISerialisableDrawableContainer targetContainer) + public void ResetDrawableTarget(SkinnableTargetContainer targetContainer) { DrawableComponentInfo.Remove(targetContainer.Target); } @@ -152,9 +152,9 @@ namespace osu.Game.Skinning /// Update serialised information for the provided target. /// /// The target container to serialise to this skin. - public void UpdateDrawableTarget(ISerialisableDrawableContainer targetContainer) + public void UpdateDrawableTarget(SkinnableTargetContainer targetContainer) { - DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSerialisedInfo().ToArray(); + DrawableComponentInfo[targetContainer.Target] = ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray(); } public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup) From b1cf6d83d89050686fb1db8dc8df65d28cf0d42b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 17:51:18 +0900 Subject: [PATCH 33/67] Move extension methods closer to serialisation classes --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 1 - osu.Game/Extensions/DrawableExtensions.cs | 41 --------------- .../SkinEditor/SkinEditorChangeHandler.cs | 1 - osu.Game/Skinning/ISerialisableDrawable.cs | 2 +- .../ISerialisableDrawableContainer.cs | 1 - .../SerialisableDrawableExtensions.cs | 51 +++++++++++++++++++ osu.Game/Skinning/SkinnableTargetContainer.cs | 1 - 7 files changed, 52 insertions(+), 46 deletions(-) create mode 100644 osu.Game/Skinning/SerialisableDrawableExtensions.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 5005cff265..339b38eacd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -13,7 +13,6 @@ using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Extensions; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Skinning.Legacy; diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index b6f74f5777..915a2292a2 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -1,11 +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 osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Extensions @@ -47,42 +43,5 @@ namespace osu.Game.Extensions /// The delta vector in Parent's coordinates. public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) => drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta); - - public static SerialisedDrawableInfo CreateSerialisedInfo(this Drawable component) => new SerialisedDrawableInfo(component); - - public static void ApplySerialisedInfo(this Drawable component, SerialisedDrawableInfo drawableInfo) - { - // todo: can probably make this better via deserialisation directly using a common interface. - component.Position = drawableInfo.Position; - component.Rotation = drawableInfo.Rotation; - component.Scale = drawableInfo.Scale; - component.Anchor = drawableInfo.Anchor; - component.Origin = drawableInfo.Origin; - - if (component is ISerialisableDrawable skinnable) - { - skinnable.UsesFixedAnchor = drawableInfo.UsesFixedAnchor; - - foreach (var (_, property) in component.GetSettingsSourceProperties()) - { - var bindable = ((IBindable)property.GetValue(component)!); - - if (!drawableInfo.Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue)) - { - // TODO: We probably want to restore default if not included in serialisation information. - // This is not simple to do as SetDefault() is only found in the typed Bindable interface right now. - continue; - } - - skinnable.CopyAdjustedSetting(bindable, settingValue); - } - } - - if (component is Container container) - { - foreach (var child in drawableInfo.Children) - container.Add(child.CreateInstance()); - } - } } } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 6c66714b79..d1a1850796 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -9,7 +9,6 @@ using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Game.Extensions; using osu.Game.Screens.Edit; using osu.Game.Skinning; diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs index 23cbda6344..65ece96a67 100644 --- a/osu.Game/Skinning/ISerialisableDrawable.cs +++ b/osu.Game/Skinning/ISerialisableDrawable.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning /// Attaching this interface to any will make it serialisable to user skins (see ). /// Adding annotated bindables will also allow serialising settings automatically. /// - /// Serialisation is done via using . + /// Serialisation is done via using . /// public interface ISerialisableDrawable : IDrawable { diff --git a/osu.Game/Skinning/ISerialisableDrawableContainer.cs b/osu.Game/Skinning/ISerialisableDrawableContainer.cs index 54d8a68c95..9f93d8a2e3 100644 --- a/osu.Game/Skinning/ISerialisableDrawableContainer.cs +++ b/osu.Game/Skinning/ISerialisableDrawableContainer.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Extensions; namespace osu.Game.Skinning { diff --git a/osu.Game/Skinning/SerialisableDrawableExtensions.cs b/osu.Game/Skinning/SerialisableDrawableExtensions.cs new file mode 100644 index 0000000000..5e8c2843a9 --- /dev/null +++ b/osu.Game/Skinning/SerialisableDrawableExtensions.cs @@ -0,0 +1,51 @@ +// 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.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; +using osu.Game.Extensions; + +namespace osu.Game.Skinning +{ + public static class SerialisableDrawableExtensions + { + public static SerialisedDrawableInfo CreateSerialisedInfo(this Drawable component) => new SerialisedDrawableInfo(component); + + public static void ApplySerialisedInfo(this Drawable component, SerialisedDrawableInfo drawableInfo) + { + // todo: can probably make this better via deserialisation directly using a common interface. + component.Position = drawableInfo.Position; + component.Rotation = drawableInfo.Rotation; + component.Scale = drawableInfo.Scale; + component.Anchor = drawableInfo.Anchor; + component.Origin = drawableInfo.Origin; + + if (component is ISerialisableDrawable skinnable) + { + skinnable.UsesFixedAnchor = drawableInfo.UsesFixedAnchor; + + foreach (var (_, property) in component.GetSettingsSourceProperties()) + { + var bindable = ((IBindable)property.GetValue(component)!); + + if (!drawableInfo.Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue)) + { + // TODO: We probably want to restore default if not included in serialisation information. + // This is not simple to do as SetDefault() is only found in the typed Bindable interface right now. + continue; + } + + skinnable.CopyAdjustedSetting(bindable, settingValue); + } + } + + if (component is Container container) + { + foreach (var child in drawableInfo.Children) + container.Add(child.CreateInstance()); + } + } + } +} diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index abed2c09b6..d29152da6c 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -8,7 +8,6 @@ using System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Skinning.Serialisation; namespace osu.Game.Skinning { From a92e42bb842ef318c84cbb10306f05042785e441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 18:31:55 +0900 Subject: [PATCH 34/67] Rename `SkinnableTargetContainer` to `SkinComponentsContainer` Also use full `SkinComponentsContainerLookup` instead of the sub-type. This will potentially be useful once we bring in per-ruleset targets. --- .../TestSceneCatchPlayerLegacySkin.cs | 2 +- .../Legacy/CatchLegacySkinTransformer.cs | 6 +++--- .../Skins/SkinDeserialisationTest.cs | 16 ++++++++-------- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 10 +++++----- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 10 +++++----- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 +- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 12 ++++++------ osu.Game/Screens/Play/HUDOverlay.cs | 6 +++--- osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Skinning/ArgonSkin.cs | 8 ++++---- osu.Game/Skinning/LegacyBeatmapSkin.cs | 6 +++--- osu.Game/Skinning/LegacySkin.cs | 6 +++--- osu.Game/Skinning/Skin.cs | 18 +++++++++--------- ...ontainer.cs => SkinComponentsContainer.cs} | 19 ++++++++++++++----- ...up.cs => SkinComponentsContainerLookup.cs} | 10 +++++----- osu.Game/Skinning/TrianglesSkin.cs | 8 ++++---- .../Tests/Visual/LegacySkinPlayerTestScene.cs | 4 ++-- 17 files changed, 77 insertions(+), 68 deletions(-) rename osu.Game/Skinning/{SkinnableTargetContainer.cs => SkinComponentsContainer.cs} (83%) rename osu.Game/Skinning/{GlobalSkinComponentLookup.cs => SkinComponentsContainerLookup.cs} (55%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 8f3f39be9b..4c1ba33aa2 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Tests if (withModifiedSkin) { AddStep("change component scale", () => Player.ChildrenOfType().First().Scale = new Vector2(2f)); - AddStep("update target", () => Player.ChildrenOfType().ForEach(LegacySkin.UpdateDrawableTarget)); + AddStep("update target", () => Player.ChildrenOfType().ForEach(LegacySkin.UpdateDrawableTarget)); AddStep("exit player", () => Player.Exit()); CreateTest(); } diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index a06435583b..6704fd82e9 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -28,11 +28,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is GlobalSkinComponentLookup targetComponent) + if (lookup is SkinComponentsContainerLookup componentLookup) { - switch (targetComponent.Lookup) + switch (componentLookup.Target) { - case GlobalSkinComponentLookup.LookupType.MainHUDComponents: + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: var components = base.GetDrawableComponent(lookup) as Container; if (providesComboCounter && components != null) diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index b897523560..5d7cbad46d 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Skins var skin = new TestSkin(new SkinInfo(), null, storage); Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2)); - Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(9)); + Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(9)); } } @@ -101,10 +101,10 @@ namespace osu.Game.Tests.Skins var skin = new TestSkin(new SkinInfo(), null, storage); Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2)); - Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(6)); - Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.SongSelect], Has.Length.EqualTo(1)); + Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(6)); + Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.SongSelect], Has.Length.EqualTo(1)); - var skinnableInfo = skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.SongSelect].First(); + var skinnableInfo = skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.SongSelect].First(); Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite))); Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name")); @@ -115,10 +115,10 @@ namespace osu.Game.Tests.Skins using (var storage = new ZipArchiveReader(stream)) { var skin = new TestSkin(new SkinInfo(), null, storage); - Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(8)); - Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter))); - Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter))); - Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress))); + Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(8)); + Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter))); + Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter))); + Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress))); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 339b38eacd..514a2d7e84 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -36,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestEmptyLegacyBeatmapSkinFallsBack() { CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null)); - AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); - AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType.MainHUDComponents, skinManager.CurrentSkin.Value)); + AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, skinManager.CurrentSkin.Value)); } protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSkin) @@ -52,9 +52,9 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource) + protected bool AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea target, ISkin expectedSource) { - var targetContainer = Player.ChildrenOfType().First(s => s.Target == target); + var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Target == target); var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer); if (actualComponentsContainer == null) @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay var actualInfo = actualComponentsContainer.CreateSerialisedInfo(); - var expectedComponentsContainer = expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)) as Container; + var expectedComponentsContainer = expectedSource.GetDrawableComponent(new SkinComponentsContainerLookup(target)) as Container; if (expectedComponentsContainer == null) return false; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 5e1412d79b..b918c5e64a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -235,8 +235,8 @@ namespace osu.Game.Tests.Visual.Gameplay createNew(); AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); - AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0); - AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0); + AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded)); AddStep("bind on update", () => { @@ -254,10 +254,10 @@ namespace osu.Game.Tests.Visual.Gameplay createNew(); AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); - AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0); + AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0); - AddStep("reload components", () => hudOverlay.ChildrenOfType().Single().Reload()); - AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType().Single().ComponentsLoaded); + AddStep("reload components", () => hudOverlay.ChildrenOfType().Single().Reload()); + AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType().Single().ComponentsLoaded); } private void createNew(Action? action = null) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 86d97b4999..62fdb67a30 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); - AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); AddStep("reload skin editor", () => { diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 25448906b2..133ec10202 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -339,25 +339,25 @@ namespace osu.Game.Overlays.SkinEditor settingsSidebar.Add(new SkinSettingsToolbox(component)); } - private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private IEnumerable availableTargets => targetScreen.ChildrenOfType(); private ISerialisableDrawableContainer? getFirstTarget() => availableTargets.FirstOrDefault(); - private ISerialisableDrawableContainer? getTarget(GlobalSkinComponentLookup.LookupType target) + private ISerialisableDrawableContainer? getTarget(SkinComponentsContainerLookup.TargetArea target) { - return availableTargets.FirstOrDefault(c => c.Target == target); + return availableTargets.FirstOrDefault(c => c.Lookup.Target == target); } private void revert() { - SkinnableTargetContainer[] targetContainers = availableTargets.ToArray(); + SkinComponentsContainer[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) { currentSkin.Value.ResetDrawableTarget(t); // add back default components - getTarget(t.Target)?.Reload(); + getTarget(t.Lookup.Target)?.Reload(); } } @@ -370,7 +370,7 @@ namespace osu.Game.Overlays.SkinEditor if (!hasBegunMutating) return; - SkinnableTargetContainer[] targetContainers = availableTargets.ToArray(); + SkinComponentsContainer[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) currentSkin.Value.UpdateDrawableTarget(t); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 4d1f0b96b6..e41afd7722 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -88,7 +88,7 @@ namespace osu.Game.Screens.Play private readonly BindableBool holdingForHUD = new BindableBool(); - private readonly SkinnableTargetContainer mainComponents; + private readonly SkinComponentsContainer mainComponents; /// /// A flow which sits at the left side of the screen to house leaderboard (and related) components. @@ -390,7 +390,7 @@ namespace osu.Game.Screens.Play } } - private partial class MainComponentsContainer : SkinnableTargetContainer + private partial class MainComponentsContainer : SkinComponentsContainer { private Bindable scoringMode; @@ -398,7 +398,7 @@ namespace osu.Game.Screens.Play private OsuConfigManager config { get; set; } public MainComponentsContainer() - : base(GlobalSkinComponentLookup.LookupType.MainHUDComponents) + : base(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.MainHUDComponents)) { RelativeSizeAxes = Axes.Both; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8786821c77..2d3d0f88d7 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -273,7 +273,7 @@ namespace osu.Game.Screens.Select } } }, - new SkinnableTargetContainer(GlobalSkinComponentLookup.LookupType.SongSelect) + new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect)) { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 71f0888c6f..7b4d51ee83 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -90,10 +90,10 @@ namespace osu.Game.Skinning switch (lookup) { - case GlobalSkinComponentLookup globalLookup: - switch (globalLookup.Lookup) + case SkinComponentsContainerLookup componentLookup: + switch (componentLookup.Target) { - case GlobalSkinComponentLookup.LookupType.SongSelect: + case SkinComponentsContainerLookup.TargetArea.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => { // do stuff when we need to. @@ -101,7 +101,7 @@ namespace osu.Game.Skinning return songSelectComponents; - case GlobalSkinComponentLookup.LookupType.MainHUDComponents: + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 8407b144f8..84ca6ed967 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -45,11 +45,11 @@ namespace osu.Game.Skinning public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is GlobalSkinComponentLookup targetComponent) + if (lookup is SkinComponentsContainerLookup targetComponent) { - switch (targetComponent.Lookup) + switch (targetComponent.Target) { - case GlobalSkinComponentLookup.LookupType.MainHUDComponents: + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: // this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet. // therefore keep the check here until fallback default legacy skin is supported. if (!this.HasFont(LegacyFont.Score)) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 284a938bce..c581fcdfb6 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -343,10 +343,10 @@ namespace osu.Game.Skinning switch (lookup) { - case GlobalSkinComponentLookup target: - switch (target.Lookup) + case SkinComponentsContainerLookup componentLookup: + switch (componentLookup.Target) { - case GlobalSkinComponentLookup.LookupType.MainHUDComponents: + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index b97b167419..977fab1b7e 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -37,9 +37,9 @@ namespace osu.Game.Skinning public SkinConfiguration Configuration { get; set; } - public IDictionary DrawableComponentInfo => drawableComponentInfo; + public IDictionary DrawableComponentInfo => drawableComponentInfo; - private readonly Dictionary drawableComponentInfo = new Dictionary(); + private readonly Dictionary drawableComponentInfo = new Dictionary(); public abstract ISample? GetSample(ISampleInfo sampleInfo); @@ -100,7 +100,7 @@ namespace osu.Game.Skinning Configuration = new SkinConfiguration(); // skininfo files may be null for default skin. - foreach (GlobalSkinComponentLookup.LookupType skinnableTarget in Enum.GetValues()) + foreach (SkinComponentsContainerLookup.TargetArea skinnableTarget in Enum.GetValues()) { string filename = $"{skinnableTarget}.json"; @@ -143,18 +143,18 @@ namespace osu.Game.Skinning /// Remove all stored customisations for the provided target. /// /// The target container to reset. - public void ResetDrawableTarget(SkinnableTargetContainer targetContainer) + public void ResetDrawableTarget(SkinComponentsContainer targetContainer) { - DrawableComponentInfo.Remove(targetContainer.Target); + DrawableComponentInfo.Remove(targetContainer.Lookup.Target); } /// /// Update serialised information for the provided target. /// /// The target container to serialise to this skin. - public void UpdateDrawableTarget(SkinnableTargetContainer targetContainer) + public void UpdateDrawableTarget(SkinComponentsContainer targetContainer) { - DrawableComponentInfo[targetContainer.Target] = ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray(); + DrawableComponentInfo[targetContainer.Lookup.Target] = ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray(); } public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup) @@ -165,8 +165,8 @@ namespace osu.Game.Skinning case SkinnableSprite.SpriteComponentLookup sprite: return this.GetAnimation(sprite.LookupName, false, false); - case GlobalSkinComponentLookup target: - if (!DrawableComponentInfo.TryGetValue(target.Lookup, out var skinnableInfo)) + case SkinComponentsContainerLookup componentLookup: + if (!DrawableComponentInfo.TryGetValue(componentLookup.Target, out var skinnableInfo)) return null; var components = new List(); diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinComponentsContainer.cs similarity index 83% rename from osu.Game/Skinning/SkinnableTargetContainer.cs rename to osu.Game/Skinning/SkinComponentsContainer.cs index d29152da6c..2620b40729 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinComponentsContainer.cs @@ -11,11 +11,20 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Skinning { - public partial class SkinnableTargetContainer : SkinReloadableDrawable, ISerialisableDrawableContainer + /// + /// A container which holds many skinnable components, with functionality to add, remove and reload layouts. + /// Used to allow user customisation of skin layouts. + /// + /// + /// This is currently used as a means of serialising skin layouts to files. + /// Currently, one json file in a skin will represent one , containing + /// the output of . + /// + public partial class SkinComponentsContainer : SkinReloadableDrawable, ISerialisableDrawableContainer { private Container? content; - public GlobalSkinComponentLookup.LookupType Target { get; } + public SkinComponentsContainerLookup Lookup { get; } public IBindableList Components => components; @@ -27,9 +36,9 @@ namespace osu.Game.Skinning private CancellationTokenSource? cancellationSource; - public SkinnableTargetContainer(GlobalSkinComponentLookup.LookupType target) + public SkinComponentsContainer(SkinComponentsContainerLookup lookup) { - Target = target; + Lookup = lookup; } public void Reload(SerialisedDrawableInfo[] skinnableInfo) @@ -46,7 +55,7 @@ namespace osu.Game.Skinning }); } - public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as Container); + public void Reload() => Reload(CurrentSkin.GetDrawableComponent(Lookup) as Container); public void Reload(Container? componentsContainer) { diff --git a/osu.Game/Skinning/GlobalSkinComponentLookup.cs b/osu.Game/Skinning/SkinComponentsContainerLookup.cs similarity index 55% rename from osu.Game/Skinning/GlobalSkinComponentLookup.cs rename to osu.Game/Skinning/SkinComponentsContainerLookup.cs index 7dcc3c4f14..ac6e98b23a 100644 --- a/osu.Game/Skinning/GlobalSkinComponentLookup.cs +++ b/osu.Game/Skinning/SkinComponentsContainerLookup.cs @@ -3,16 +3,16 @@ namespace osu.Game.Skinning { - public class GlobalSkinComponentLookup : ISkinComponentLookup + public class SkinComponentsContainerLookup : ISkinComponentLookup { - public readonly LookupType Lookup; + public readonly TargetArea Target; - public GlobalSkinComponentLookup(LookupType lookup) + public SkinComponentsContainerLookup(TargetArea target) { - Lookup = lookup; + Target = target; } - public enum LookupType + public enum TargetArea { MainHUDComponents, SongSelect diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 7de2b124e7..5e0712012e 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -68,10 +68,10 @@ namespace osu.Game.Skinning switch (lookup) { - case GlobalSkinComponentLookup target: - switch (target.Lookup) + case SkinComponentsContainerLookup componentLookup: + switch (componentLookup.Target) { - case GlobalSkinComponentLookup.LookupType.SongSelect: + case SkinComponentsContainerLookup.TargetArea.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => { // do stuff when we need to. @@ -79,7 +79,7 @@ namespace osu.Game.Skinning return songSelectComponents; - case GlobalSkinComponentLookup.LookupType.MainHUDComponents: + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs index c5efdf36b4..2e254f5b95 100644 --- a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs @@ -45,13 +45,13 @@ namespace osu.Game.Tests.Visual private void addResetTargetsStep() { - AddStep("reset targets", () => this.ChildrenOfType().ForEach(t => + AddStep("reset targets", () => this.ChildrenOfType().ForEach(t => { LegacySkin.ResetDrawableTarget(t); t.Reload(); })); - AddUntilStep("wait for components to load", () => this.ChildrenOfType().All(t => t.ComponentsLoaded)); + AddUntilStep("wait for components to load", () => this.ChildrenOfType().All(t => t.ComponentsLoaded)); } public partial class SkinProvidingPlayer : TestPlayer From 08ed174f61d3547d8019af4a436309e8b2dc1486 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 18:48:13 +0900 Subject: [PATCH 35/67] Change `GameplaySkinComponentLookup`'s generic to always be an `enum` And document the class better. --- osu.Game/Skinning/GameplaySkinComponentLookup.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/GameplaySkinComponentLookup.cs b/osu.Game/Skinning/GameplaySkinComponentLookup.cs index 9247ca0e4d..984df3f5bc 100644 --- a/osu.Game/Skinning/GameplaySkinComponentLookup.cs +++ b/osu.Game/Skinning/GameplaySkinComponentLookup.cs @@ -1,12 +1,23 @@ // 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.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Skinning { + /// + /// A lookup typed intended for use for skinnable gameplay components (not HUD level components). + /// + /// + /// The most common usage of this class is for ruleset-specific skinning implementations, but it can also be used directly + /// (see 's usage for ) where ruleset-agnostic elements are required. + /// + /// An enum lookup type. public class GameplaySkinComponentLookup : ISkinComponentLookup - where T : notnull + where T : Enum { public readonly T Component; @@ -16,7 +27,7 @@ namespace osu.Game.Skinning } protected virtual string RulesetPrefix => string.Empty; - protected virtual string ComponentName => Component.ToString() ?? string.Empty; + protected virtual string ComponentName => Component.ToString(); public string LookupName => string.Join('/', new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); From 0b25f7baeb1e71be25fb7f3db18760493ded2d7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 15:27:30 +0900 Subject: [PATCH 36/67] Reword and fix typos in some new xmldoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Skinning/GameplaySkinComponentLookup.cs | 2 +- osu.Game/Skinning/ISkinSource.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/GameplaySkinComponentLookup.cs b/osu.Game/Skinning/GameplaySkinComponentLookup.cs index 984df3f5bc..a44bf3a43d 100644 --- a/osu.Game/Skinning/GameplaySkinComponentLookup.cs +++ b/osu.Game/Skinning/GameplaySkinComponentLookup.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Skinning { /// - /// A lookup typed intended for use for skinnable gameplay components (not HUD level components). + /// A lookup type intended for use for skinnable gameplay components (not HUD level components). /// /// /// The most common usage of this class is for ruleset-specific skinning implementations, but it can also be used directly diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index b05d52d47c..0dfc99e3bc 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; namespace osu.Game.Skinning { /// - /// An abstract skin implementation which generally provides access to more than one skins (with fallback logic). + /// An abstract skin implementation, whose primary purpose is to properly handle component fallback across multiple layers of skins (e.g.: beatmap skin, user skin, default skin). /// /// /// Common usage is to do an initial lookup via , and use the returned From eea0cd3cf8c29d200cdb685f1c1cd75800165f88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 15:31:35 +0900 Subject: [PATCH 37/67] Reword xmldoc on `ISerialisableDrawable` to make less skin-centric --- osu.Game/Skinning/ISerialisableDrawable.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs index 65ece96a67..503b44c2dd 100644 --- a/osu.Game/Skinning/ISerialisableDrawable.cs +++ b/osu.Game/Skinning/ISerialisableDrawable.cs @@ -10,13 +10,14 @@ using osu.Game.Configuration; namespace osu.Game.Skinning { /// - /// A drawable which can be serialised to a skin, placed and customised via the skin layout editor. + /// A drawable which is intended to be serialised to . /// /// - /// Attaching this interface to any will make it serialisable to user skins (see ). - /// Adding annotated bindables will also allow serialising settings automatically. + /// This is currently used exclusively for serialisation to a skin, and leaned on heavily to allow placement and customisation in the skin layout editor. + /// That said, it is intended to be flexible enough to potentially be used in other places we want to serialise drawables in the future. /// - /// Serialisation is done via using . + /// Attaching this interface to any will make it serialisable via . + /// Adding annotated bindables will also allow serialising settings automatically. /// public interface ISerialisableDrawable : IDrawable { From 76f7accd1378be9ca051c0cf3e1b2936d9b14eaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 15:33:56 +0900 Subject: [PATCH 38/67] Standardise all local `SkinComponentsContainerLookup` variables to `containerLookup` --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 4 ++-- osu.Game/Skinning/ArgonSkin.cs | 4 ++-- osu.Game/Skinning/LegacyBeatmapSkin.cs | 4 ++-- osu.Game/Skinning/LegacySkin.cs | 4 ++-- osu.Game/Skinning/Skin.cs | 4 ++-- osu.Game/Skinning/TrianglesSkin.cs | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 6704fd82e9..fb8af9bdb6 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is SkinComponentsContainerLookup componentLookup) + if (lookup is SkinComponentsContainerLookup containerLookup) { - switch (componentLookup.Target) + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: var components = base.GetDrawableComponent(lookup) as Container; diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 7b4d51ee83..737b222b9d 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -90,8 +90,8 @@ namespace osu.Game.Skinning switch (lookup) { - case SkinComponentsContainerLookup componentLookup: - switch (componentLookup.Target) + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 84ca6ed967..1cbfda16cf 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -45,9 +45,9 @@ namespace osu.Game.Skinning public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is SkinComponentsContainerLookup targetComponent) + if (lookup is SkinComponentsContainerLookup containerLookup) { - switch (targetComponent.Target) + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: // this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet. diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index c581fcdfb6..98d9f1d617 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -343,8 +343,8 @@ namespace osu.Game.Skinning switch (lookup) { - case SkinComponentsContainerLookup componentLookup: - switch (componentLookup.Target) + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 977fab1b7e..f261937012 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -165,8 +165,8 @@ namespace osu.Game.Skinning case SkinnableSprite.SpriteComponentLookup sprite: return this.GetAnimation(sprite.LookupName, false, false); - case SkinComponentsContainerLookup componentLookup: - if (!DrawableComponentInfo.TryGetValue(componentLookup.Target, out var skinnableInfo)) + case SkinComponentsContainerLookup containerLookup: + if (!DrawableComponentInfo.TryGetValue(containerLookup.Target, out var skinnableInfo)) return null; var components = new List(); diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 5e0712012e..12cdb71744 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -68,8 +68,8 @@ namespace osu.Game.Skinning switch (lookup) { - case SkinComponentsContainerLookup componentLookup: - switch (componentLookup.Target) + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => From 81dcc105a96fb72b8cbd066432fc70fef532620a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 15:34:41 +0900 Subject: [PATCH 39/67] Rename left-over `skinnable` naming in `SerialisedDrawableExtensions` --- osu.Game/Skinning/SerialisableDrawableExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SerialisableDrawableExtensions.cs b/osu.Game/Skinning/SerialisableDrawableExtensions.cs index 5e8c2843a9..51b57a000d 100644 --- a/osu.Game/Skinning/SerialisableDrawableExtensions.cs +++ b/osu.Game/Skinning/SerialisableDrawableExtensions.cs @@ -22,9 +22,9 @@ namespace osu.Game.Skinning component.Anchor = drawableInfo.Anchor; component.Origin = drawableInfo.Origin; - if (component is ISerialisableDrawable skinnable) + if (component is ISerialisableDrawable serialisableDrawable) { - skinnable.UsesFixedAnchor = drawableInfo.UsesFixedAnchor; + serialisableDrawable.UsesFixedAnchor = drawableInfo.UsesFixedAnchor; foreach (var (_, property) in component.GetSettingsSourceProperties()) { @@ -37,7 +37,7 @@ namespace osu.Game.Skinning continue; } - skinnable.CopyAdjustedSetting(bindable, settingValue); + serialisableDrawable.CopyAdjustedSetting(bindable, settingValue); } } From ce9ef3bc3c0092b99c904ba89c0a7ce99be6cab4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 15:47:20 +0900 Subject: [PATCH 40/67] Always create `ResumeOverlay`, with `UseResumeOverlay` flag only affecting whether it is displayed or not --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index c839062875..d57aec1271 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -194,16 +194,11 @@ namespace osu.Game.Rulesets.UI Audio = audioContainer; - if (UseResumeOverlay) + if ((ResumeOverlay = CreateResumeOverlay()) != null) { - if ((ResumeOverlay = CreateResumeOverlay()) == null) - UseResumeOverlay = false; - else - { - AddInternal(CreateInputManager() - .WithChild(CreatePlayfieldAdjustmentContainer() - .WithChild(ResumeOverlay))); - } + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); } applyRulesetMods(Mods, config); @@ -515,7 +510,10 @@ namespace osu.Game.Rulesets.UI /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. /// - /// Defaults to true, but will become false if the ruleset fails to return a non-null overlay via . + /// + /// Defaults to true. + /// Even if true, will not have any effect if the ruleset does not have a resume overlay (see ). + /// public bool UseResumeOverlay { get; set; } = true; /// @@ -542,6 +540,11 @@ namespace osu.Game.Rulesets.UI } } + /// + /// Create an optional resume overlay, which is displayed when a player requests to resume gameplay during non-break time. + /// This can be used to force the player to return their hands / cursor to the position they left off, to avoid players + /// using pauses as a means of adjusting their inputs (aka "pause buffering"). + /// protected virtual ResumeOverlay CreateResumeOverlay() => null; /// From 9d02a2ef0e8a17398459403ac71353d4688ad4ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 17:37:46 +0900 Subject: [PATCH 41/67] Apply NRT to `GamepleSampleTriggerSource` tests --- .../Gameplay/TestSceneGameplaySampleTriggerSource.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index e184d50d7c..a9b469fd86 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Audio; @@ -20,10 +18,10 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneGameplaySampleTriggerSource : PlayerTestScene { - private TestGameplaySampleTriggerSource sampleTriggerSource; + private TestGameplaySampleTriggerSource sampleTriggerSource = null!; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - private Beatmap beatmap; + private Beatmap beatmap = null!; protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { @@ -80,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestCorrectHitObject() { - HitObjectLifetimeEntry nextObjectEntry = null; + HitObjectLifetimeEntry? nextObjectEntry = null; AddAssert("no alive objects", () => getNextAliveObject() == null); @@ -103,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("hit first hitobject", () => { InputManager.Click(MouseButton.Left); - return nextObjectEntry.Result?.HasResult == true; + return nextObjectEntry?.Result?.HasResult == true; }); AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); @@ -115,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); } - private DrawableHitObject getNextAliveObject() => + private DrawableHitObject? getNextAliveObject() => Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(); [Test] From 979c079f8b5f852575e9722303173d28facbd31a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 17:58:32 +0900 Subject: [PATCH 42/67] Refactor `GameplaySampleTriggerSource` test to not be realtime dependent --- .../TestSceneGameplaySampleTriggerSource.cs | 79 +++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index a9b469fd86..e7bdf7b9ba 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -1,8 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -12,6 +16,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Storyboards; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -23,6 +28,12 @@ namespace osu.Game.Tests.Visual.Gameplay private Beatmap beatmap = null!; + [Resolved] + private AudioManager audio { get; set; } = null!; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio); + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { beatmap = new Beatmap @@ -37,12 +48,13 @@ namespace osu.Game.Tests.Visual.Gameplay const double start_offset = 8000; const double spacing = 2000; + // intentionally start objects a bit late so we can test the case of no alive objects. double t = start_offset; + beatmap.HitObjects.AddRange(new[] { new HitCircle { - // intentionally start objects a bit late so we can test the case of no alive objects. StartTime = t += spacing, Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } }, @@ -78,41 +90,64 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestCorrectHitObject() { - HitObjectLifetimeEntry? nextObjectEntry = null; + waitForAliveObjectIndex(null); - AddAssert("no alive objects", () => getNextAliveObject() == null); + seekBeforeIndex(0); + waitForAliveObjectIndex(0); + checkValidObjectIndex(0); - AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]); + AddAssert("first object not hit", () => getNextAliveObject()?.Entry?.Result?.HasResult != true); - AddUntilStep("get next object", () => + AddStep("hit first object", () => { - var nextDrawableObject = getNextAliveObject(); + var next = getNextAliveObject(); - if (nextDrawableObject != null) + if (next != null) { - nextObjectEntry = nextDrawableObject.Entry; - InputManager.MoveMouseTo(nextDrawableObject.ScreenSpaceDrawQuad.Centre); - return true; + Debug.Assert(next.Entry?.Result?.HasResult != true); + + InputManager.MoveMouseTo(next.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); } - - return false; }); - AddUntilStep("hit first hitobject", () => - { - InputManager.Click(MouseButton.Left); - return nextObjectEntry?.Result?.HasResult == true; - }); + AddAssert("first object hit", () => getNextAliveObject()?.Entry?.Result?.HasResult == true); - AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); + checkValidObjectIndex(1); - AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[2]); - AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + // Still object 1 as it's not hit yet. + seekBeforeIndex(1); + waitForAliveObjectIndex(1); + checkValidObjectIndex(1); - AddUntilStep("no alive objects", () => getNextAliveObject() == null); - AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + seekBeforeIndex(2); + waitForAliveObjectIndex(2); + checkValidObjectIndex(2); + + seekBeforeIndex(3); + waitForAliveObjectIndex(3); + checkValidObjectIndex(3); + + AddStep("Seek into future", () => Beatmap.Value.Track.Seek(beatmap.HitObjects.Last().GetEndTime() + 10000)); + + waitForAliveObjectIndex(null); + checkValidObjectIndex(3); } + private void seekBeforeIndex(int index) => + AddStep($"seek to just before object {index}", () => Beatmap.Value.Track.Seek(beatmap.HitObjects[index].StartTime - 100)); + + private void waitForAliveObjectIndex(int? index) + { + if (index == null) + AddUntilStep("wait for no alive objects", getNextAliveObject, () => Is.Null); + else + AddUntilStep($"wait for next alive to be {index}", () => getNextAliveObject()?.HitObject, () => Is.EqualTo(beatmap.HitObjects[index.Value])); + } + + private void checkValidObjectIndex(int index) => + AddAssert($"check valid object is {index}", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[index])); + private DrawableHitObject? getNextAliveObject() => Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(); From 394d368f165431e7bb58bc4d365ee7f7fbb6614b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 18:45:22 +0900 Subject: [PATCH 43/67] Fix song select potentially updating background parameters when not the current screen --- .../Screens/Play/ScreenWithBeatmapBackground.cs | 11 ++++++++--- osu.Game/Screens/Select/SongSelect.cs | 16 ++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index d6c8a0ad6a..a5d1961bd8 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.Diagnostics; +using osu.Framework.Screens; using osu.Game.Screens.Backgrounds; namespace osu.Game.Screens.Play @@ -12,6 +12,11 @@ namespace osu.Game.Screens.Play { protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); - public void ApplyToBackground(Action action) => base.ApplyToBackground(b => action.Invoke((BackgroundScreenBeatmap)b)); + public void ApplyToBackground(Action action) + { + Debug.Assert(this.IsCurrentScreen()); + + base.ApplyToBackground(b => action.Invoke((BackgroundScreenBeatmap)b)); + } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8546dfeeaa..8a9a20261e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -765,14 +765,18 @@ namespace osu.Game.Screens.Select /// The working beatmap. private void updateComponentFromBeatmap(WorkingBeatmap beatmap) { - ApplyToBackground(backgroundModeBeatmap => + // If not the current screen, this will be applied in OnResuming. + if (this.IsCurrentScreen()) { - backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.IgnoreUserSettings.Value = true; - backgroundModeBeatmap.FadeColour(Color4.White, 250); + ApplyToBackground(backgroundModeBeatmap => + { + backgroundModeBeatmap.Beatmap = beatmap; + backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.FadeColour(Color4.White, 250); - applyBlurToBackground(backgroundModeBeatmap); - }); + applyBlurToBackground(backgroundModeBeatmap); + }); + } beatmapInfoWedge.Beatmap = beatmap; From cb7df7282b78e9fd3abf05aac19d0fedf515416f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 20:01:59 +0900 Subject: [PATCH 44/67] Apply NRT to `SerialisedDrawableInfo` --- osu.Game/Skinning/SerialisedDrawableInfo.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index f571f1a945..12f6d3ac7e 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -28,7 +26,7 @@ namespace osu.Game.Skinning [Serializable] public sealed class SerialisedDrawableInfo { - public Type Type { get; set; } + public Type Type { get; set; } = null!; public Vector2 Position { get; set; } From 5bdc5dfadde8c7b4d32abbd81e2555718f803366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 19:02:51 +0100 Subject: [PATCH 45/67] Add one more assert to keep coverage from previous implementation --- .../Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index e7bdf7b9ba..6770309a7d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -91,6 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestCorrectHitObject() { waitForAliveObjectIndex(null); + checkValidObjectIndex(0); seekBeforeIndex(0); waitForAliveObjectIndex(0); From ad5132ed4194380bae37331b2746333a5acead3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 20:47:51 +0100 Subject: [PATCH 46/67] Remove redundant conditional access qualifier It is impossible for the callback passed to `ApplyToBackground()` to receive a null reference. See `OsuScreen.ApplyToBackground()` - if the background to call the callback on were `null`, then an `InvalidOperationException` would be thrown instead. --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d9062da8aa..4f7e4add32 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -417,7 +417,7 @@ namespace osu.Game.Screens.Play lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in) - ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); + ApplyToBackground(b => b.FadeColour(Color4.White, 800, Easing.OutQuint)); } protected virtual void ContentOut() From b8084a15ebc9a5bb278b46c7ea31d478063245e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 21:26:01 +0100 Subject: [PATCH 47/67] Revert `ResumeOverlay` setter accessibility change --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d57aec1271..2f8b101cfc 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.UI /// /// An optional overlay used when resuming gameplay from a paused state. /// - public ResumeOverlay ResumeOverlay { get; set; } + public ResumeOverlay ResumeOverlay { get; protected set; } /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. From 1a63ca9ecedd280d19b6dbdf48a311c5c56c1ce3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 14:18:05 +0900 Subject: [PATCH 48/67] Add xmldoc around `SkinComponentsContainerLookup` --- osu.Game/Skinning/SkinComponentsContainer.cs | 3 +++ osu.Game/Skinning/SkinComponentsContainerLookup.cs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Skinning/SkinComponentsContainer.cs b/osu.Game/Skinning/SkinComponentsContainer.cs index 2620b40729..7d7a4965f6 100644 --- a/osu.Game/Skinning/SkinComponentsContainer.cs +++ b/osu.Game/Skinning/SkinComponentsContainer.cs @@ -24,6 +24,9 @@ namespace osu.Game.Skinning { private Container? content; + /// + /// The lookup criteria which will be used to retrieve components from the active skin. + /// public SkinComponentsContainerLookup Lookup { get; } public IBindableList Components => components; diff --git a/osu.Game/Skinning/SkinComponentsContainerLookup.cs b/osu.Game/Skinning/SkinComponentsContainerLookup.cs index ac6e98b23a..b0b87f18b5 100644 --- a/osu.Game/Skinning/SkinComponentsContainerLookup.cs +++ b/osu.Game/Skinning/SkinComponentsContainerLookup.cs @@ -3,8 +3,14 @@ namespace osu.Game.Skinning { + /// + /// Represents a lookup of a collection of elements that make up a particular skinnable of the game. + /// public class SkinComponentsContainerLookup : ISkinComponentLookup { + /// + /// The target area / layer of the game for which skin components will be returned. + /// public readonly TargetArea Target; public SkinComponentsContainerLookup(TargetArea target) @@ -12,6 +18,9 @@ namespace osu.Game.Skinning Target = target; } + /// + /// Represents a particular area or part of a game screen whose layout can be customised using the skin editor. + /// public enum TargetArea { MainHUDComponents, From 16d94b4ea2ac405a5988b6ebf11c5a7192a0d79f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 17:51:52 +0900 Subject: [PATCH 49/67] Improve visuals of skin blueprint --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 69 ++++++++++++------- .../Compose/Components/SelectionHandler.cs | 7 +- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 55ea362873..fedb1ac8e0 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -5,12 +5,15 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -29,35 +32,36 @@ namespace osu.Game.Overlays.SkinEditor protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; - [Resolved] - private OsuColour colours { get; set; } = null!; - public SkinBlueprint(ISerialisableDrawable component) : base(component) { } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { InternalChildren = new Drawable[] { box = new Container { + Padding = new MarginPadding(-SkinSelectionHandler.INFLATE_SIZE), Children = new Drawable[] { outlineBox = new Container { RelativeSizeAxes = Axes.Both, Masking = true, - BorderThickness = 3, - BorderColour = Color4.White, + CornerRadius = 3, + BorderThickness = SelectionBox.BORDER_RADIUS / 2, + BorderColour = ColourInfo.GradientVertical(colours.Pink4.Darken(0.4f), colours.Pink4), Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0f, + Blending = BlendingParameters.Additive, + Alpha = 0.2f, + Colour = ColourInfo.GradientVertical(colours.Pink2, colours.Pink4), AlwaysPresent = true, }, } @@ -100,9 +104,6 @@ namespace osu.Game.Overlays.SkinEditor private void updateSelectedState() { - outlineBox.FadeColour(colours.Pink.Opacity(IsSelected ? 1 : 0.5f), 200, Easing.OutQuint); - outlineBox.Child.FadeTo(IsSelected ? 0.2f : 0, 200, Easing.OutQuint); - anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint); } @@ -134,39 +135,47 @@ namespace osu.Game.Overlays.SkinEditor { private readonly Drawable drawable; - private readonly Box originBox; + private Drawable originBox = null!; - private readonly Box anchorBox; - private readonly Box anchorLine; + private Drawable anchorBox = null!; + private Drawable anchorLine = null!; public AnchorOriginVisualiser(Drawable drawable) { this.drawable = drawable; + } - InternalChildren = new Drawable[] + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Color4 anchorColour = colours.Red1; + Color4 originColour = colours.Red3; + + InternalChildren = new[] { - anchorLine = new Box + anchorLine = new Circle { - Height = 2, + Height = 3f, Origin = Anchor.CentreLeft, - Colour = Color4.Yellow, - EdgeSmoothness = Vector2.One + Colour = ColourInfo.GradientHorizontal(originColour.Opacity(0.5f), originColour), }, - originBox = new Box + originBox = new Circle { - Colour = Color4.Red, + Colour = originColour, Origin = Anchor.Centre, - Size = new Vector2(5), + Size = new Vector2(7), }, - anchorBox = new Box + anchorBox = new Circle { - Colour = Color4.Red, + Colour = anchorColour, Origin = Anchor.Centre, - Size = new Vector2(5), + Size = new Vector2(10), }, }; } + private Vector2? anchorPosition; + protected override void Update() { base.Update(); @@ -174,8 +183,18 @@ namespace osu.Game.Overlays.SkinEditor if (drawable.Parent == null) return; + var newAnchor = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this); + + anchorPosition ??= newAnchor; + + anchorPosition = + new Vector2( + (float)Interpolation.DampContinuously(anchorPosition.Value.X, newAnchor.X, 25, Clock.ElapsedFrameTime), + (float)Interpolation.DampContinuously(anchorPosition.Value.Y, newAnchor.Y, 25, Clock.ElapsedFrameTime) + ); + originBox.Position = drawable.ToSpaceOfOtherDrawable(drawable.OriginPosition, this); - anchorBox.Position = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this); + anchorBox.Position = anchorPosition.Value; var point1 = ToLocalSpace(anchorBox.ScreenSpaceDrawQuad.Centre); var point2 = ToLocalSpace(originBox.ScreenSpaceDrawQuad.Centre); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index a0ac99fec2..9e4fb26688 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -32,6 +32,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public abstract partial class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IKeyBindingHandler, IHasContextMenu { + /// + /// How much padding around the selection area is added. + /// + public const float INFLATE_SIZE = 5; + /// /// The currently selected blueprints. /// Should be used when operations are dealing directly with the visible blueprints. @@ -346,7 +351,7 @@ namespace osu.Game.Screens.Edit.Compose.Components for (int i = 1; i < selectedBlueprints.Count; i++) selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(selectedBlueprints[i].SelectionQuad).AABBFloat); - selectionRect = selectionRect.Inflate(5f); + selectionRect = selectionRect.Inflate(INFLATE_SIZE); SelectionBox.Position = selectionRect.Location; SelectionBox.Size = selectionRect.Size; From 6c61c5f4a871b68458487a348aef07c33147468d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 18:17:35 +0900 Subject: [PATCH 50/67] Fix selection on the edge of blueprints (in the new inflation area) failing --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index fedb1ac8e0..0ee9f35a62 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -32,6 +32,18 @@ namespace osu.Game.Overlays.SkinEditor protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; + private Quad drawableQuad; + + public override Quad ScreenSpaceDrawQuad => drawableQuad; + public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; + + public override bool Contains(Vector2 screenSpacePos) => drawableQuad.Contains(screenSpacePos); + + public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition); + + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => + drawableQuad.Contains(screenSpacePos); + public SkinBlueprint(ISerialisableDrawable component) : base(component) { @@ -44,7 +56,6 @@ namespace osu.Game.Overlays.SkinEditor { box = new Container { - Padding = new MarginPadding(-SkinSelectionHandler.INFLATE_SIZE), Children = new Drawable[] { outlineBox = new Container @@ -107,28 +118,21 @@ namespace osu.Game.Overlays.SkinEditor anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint); } - private Quad drawableQuad; - - public override Quad ScreenSpaceDrawQuad => drawableQuad; - protected override void Update() { base.Update(); - drawableQuad = drawable.ScreenSpaceDrawQuad; - var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad); + drawableQuad = drawable.ToScreenSpace( + drawable.DrawRectangle + .Inflate(SkinSelectionHandler.INFLATE_SIZE)); - box.Position = drawable.ToSpaceOfOtherDrawable(Vector2.Zero, this); - box.Size = quad.Size; + var localSpaceQuad = ToLocalSpace(drawableQuad); + + box.Position = localSpaceQuad.TopLeft; + box.Size = localSpaceQuad.Size; box.Rotation = drawable.Rotation; box.Scale = new Vector2(MathF.Sign(drawable.Scale.X), MathF.Sign(drawable.Scale.Y)); } - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); - - public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition); - - public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; } internal partial class AnchorOriginVisualiser : CompositeDrawable From 814080d9823460fbf93c15ed9bcc1bf141b75308 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 17:56:41 +0900 Subject: [PATCH 51/67] Only show blueprint labels when hovering or selected --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 893bc4bac2..9d2c8368e7 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Edit; @@ -25,6 +26,8 @@ namespace osu.Game.Overlays.SkinEditor private AnchorOriginVisualiser anchorOriginVisualiser = null!; + private OsuSpriteText label = null!; + private Drawable drawable => (Drawable)Item; protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; @@ -62,7 +65,7 @@ namespace osu.Game.Overlays.SkinEditor }, } }, - new OsuSpriteText + label = new OsuSpriteText { Text = Item.GetType().Name, Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), @@ -86,6 +89,18 @@ namespace osu.Game.Overlays.SkinEditor this.FadeInFromZero(200, Easing.OutQuint); } + protected override bool OnHover(HoverEvent e) + { + updateSelectedState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateSelectedState(); + base.OnHoverLost(e); + } + protected override void OnSelected() { // base logic hides selected blueprints when not selected, but skin blueprints don't do that. @@ -104,6 +119,7 @@ namespace osu.Game.Overlays.SkinEditor outlineBox.Child.FadeTo(IsSelected ? 0.2f : 0, 200, Easing.OutQuint); anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint); + label.FadeTo(IsSelected || IsHovered ? 1 : 0, 200, Easing.OutQuint); } private Quad drawableQuad; From 5ed038fbb3b0cddf07a460223312ea52c2ca3d1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 16:44:48 +0900 Subject: [PATCH 52/67] Improve the feel of hovering toolbox component items --- .../SkinEditor/SkinComponentToolbox.cs | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 28ceaf09fc..624841a3bc 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -2,12 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Logging; -using osu.Game.Graphics; +using osu.Framework.Threading; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; @@ -65,7 +66,8 @@ namespace osu.Game.Overlays.SkinEditor fill.Add(new ToolboxComponentButton(instance, target) { - RequestPlacement = t => RequestPlacement?.Invoke(t) + RequestPlacement = t => RequestPlacement?.Invoke(t), + Expanding = contractOtherButtons, }); } catch (DependencyNotRegisteredException) @@ -79,15 +81,29 @@ namespace osu.Game.Overlays.SkinEditor } } + private void contractOtherButtons(ToolboxComponentButton obj) + { + foreach (var b in fill.OfType()) + { + if (b == obj) + continue; + + b.Contract(); + } + } + public partial class ToolboxComponentButton : OsuButton { public Action? RequestPlacement; + public Action? Expanding; private readonly Drawable component; private readonly CompositeDrawable? dependencySource; private Container innerContainer = null!; + private ScheduledDelegate? expandContractAction; + private const float contracted_size = 60; private const float expanded_size = 120; @@ -102,20 +118,44 @@ namespace osu.Game.Overlays.SkinEditor Height = contracted_size; } + private const double animation_duration = 500; + protected override bool OnHover(HoverEvent e) { - this.Delay(300).ResizeHeightTo(expanded_size, 500, Easing.OutQuint); + expandContractAction = Scheduler.AddDelayed(() => + { + this.ResizeHeightTo(expanded_size, animation_duration, Easing.OutQuint); + Expanding?.Invoke(this); + }, 100); + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - this.ResizeHeightTo(contracted_size, 500, Easing.OutQuint); + + expandContractAction?.Cancel(); + // If no other component is selected for too long, force a contract. + // Otherwise we will generally contract when Contract() is called from outside. + expandContractAction = Scheduler.AddDelayed(Contract, 1000); + } + + public void Contract() + { + // Cheap debouncing to avoid stacking animations. + // The only place this is nulled is at the end of this method. + if (expandContractAction == null) + return; + + this.ResizeHeightTo(contracted_size, animation_duration, Easing.OutQuint); + + expandContractAction?.Cancel(); + expandContractAction = null; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider) { BackgroundColour = colourProvider.Background3; From dbb366e2792e0224cff5d5a0fffc84316b7dc76c Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 17 Feb 2023 22:32:03 +0900 Subject: [PATCH 53/67] CompletionText can be a LocalisableString I can't find a reason for not doing this, probably this was forgotten in https://github.com/ppy/osu/pull/15440 --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 5cce0f8c5b..e6662e2179 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Notifications } } - public string CompletionText { get; set; } = "Task has completed!"; + public LocalisableString CompletionText { get; set; } = "Task has completed!"; private float progress; From 449e5fa6f82d10a9006db2ed5d96ffbe9ebd0a26 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Feb 2023 22:09:52 +0300 Subject: [PATCH 54/67] Rename one more left-over `skinnable` naming --- osu.Game/Skinning/SerialisedDrawableInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index 12f6d3ac7e..29078f0d74 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -64,8 +64,8 @@ namespace osu.Game.Skinning Anchor = component.Anchor; Origin = component.Origin; - if (component is ISerialisableDrawable skinnable) - UsesFixedAnchor = skinnable.UsesFixedAnchor; + if (component is ISerialisableDrawable serialisableDrawable) + UsesFixedAnchor = serialisableDrawable.UsesFixedAnchor; foreach (var (_, property) in component.GetSettingsSourceProperties()) { From b390fdb8ccb46f8f257cce6c1a9af7b6281b05c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Feb 2023 21:51:19 +0100 Subject: [PATCH 55/67] Remove unused field --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 0ee9f35a62..2cb14814d1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -24,8 +24,6 @@ namespace osu.Game.Overlays.SkinEditor { private Container box = null!; - private Container outlineBox = null!; - private AnchorOriginVisualiser anchorOriginVisualiser = null!; private Drawable drawable => (Drawable)Item; @@ -58,7 +56,7 @@ namespace osu.Game.Overlays.SkinEditor { Children = new Drawable[] { - outlineBox = new Container + new Container { RelativeSizeAxes = Axes.Both, Masking = true, From 2aa4481f6802feed215633d62c43044aa5ef38cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Feb 2023 22:54:11 +0100 Subject: [PATCH 56/67] Fix toolbox items spontaneously contracting after briefly losing hover Reproduction scenario: 1. Hover a toolbox item 2. Unhover the item, but do not hover any other item (can be done by exiting the toolbox completely to the right) 3. Come back to the item hovered in step (1) 4. The item would spontaneously contract after a second --- osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 624841a3bc..f5726bf427 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -122,6 +122,7 @@ namespace osu.Game.Overlays.SkinEditor protected override bool OnHover(HoverEvent e) { + expandContractAction?.Cancel(); expandContractAction = Scheduler.AddDelayed(() => { this.ResizeHeightTo(expanded_size, animation_duration, Easing.OutQuint); From ddd37bb3190419cc93b9ea49e4ad14e205b61efb Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sat, 18 Feb 2023 19:43:45 +0100 Subject: [PATCH 57/67] Add setting to disable automatic seeking after object placement --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Localisation/EditorStrings.cs | 5 +++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++++++++- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 565a919fb8..a4544200c7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -178,6 +178,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f); SetDefault(OsuSetting.EditorShowHitMarkers, true); + SetDefault(OsuSetting.EditorSeekToHitobject, true); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -374,6 +375,7 @@ namespace osu.Game.Configuration SeasonalBackgroundMode, EditorWaveformOpacity, EditorShowHitMarkers, + EditorSeekToHitobject, DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 96c08aa6f8..65cecd27d6 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -19,6 +19,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers"); + /// + /// "Seek to Object after placement" + /// + public static LocalisableString SeekToHitobject => new TranslatableString(getKey(@"seek_to_hitobject"), @"Seek to Object after placement"); + /// /// "Timing" /// diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b5b7400f64..528088dbda 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -17,6 +17,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Overlays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; @@ -70,6 +71,7 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; private IBindable hasTiming; + protected Bindable SeekToHitobject { get; private set; } protected HitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -80,8 +82,10 @@ namespace osu.Game.Rulesets.Edit dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuConfigManager config) { + SeekToHitobject = config.GetBindable(OsuSetting.EditorSeekToHitobject); + Config = Dependencies.Get().GetConfigFor(Ruleset); try @@ -365,6 +369,9 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); + // conditionally seek based on setting + if (!SeekToHitobject.Value) return; + if (EditorClock.CurrentTime < hitObject.StartTime) EditorClock.SeekSmoothlyTo(hitObject.StartTime); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bd133383d1..d6c698c139 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -185,6 +185,7 @@ namespace osu.Game.Screens.Edit private Bindable editorBackgroundDim; private Bindable editorHitMarkers; + private Bindable editorSeekToHitobject; public Editor(EditorLoader loader = null) { @@ -272,6 +273,7 @@ namespace osu.Game.Screens.Edit editorBackgroundDim = config.GetBindable(OsuSetting.EditorDim); editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers); + editorSeekToHitobject = config.GetBindable(OsuSetting.EditorSeekToHitobject); AddInternal(new OsuContextMenuContainer { @@ -329,6 +331,10 @@ namespace osu.Game.Screens.Edit new ToggleMenuItem(EditorStrings.ShowHitMarkers) { State = { BindTarget = editorHitMarkers }, + }, + new ToggleMenuItem(EditorStrings.SeekToHitobject) + { + State = { BindTarget = editorSeekToHitobject }, } } }, From 55e9a71f388a7c5aed00f8fadcb1ce76bd725a73 Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sat, 18 Feb 2023 20:42:13 +0100 Subject: [PATCH 58/67] Add test for seeking setting in mania placement test --- .../TestScenePlacementBeforeTrackStart.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs index 00dd75ceee..4c48a361b8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs @@ -3,8 +3,11 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Tests.Visual; using osuTK.Input; @@ -14,6 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); + [Resolved] + private OsuConfigManager config { get; set; } = null!; + [Test] public void TestPlacement() { @@ -26,5 +32,36 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("Click", () => InputManager.Click(MouseButton.Left)); AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0)); } + + [Test] + public void TestSeekOnNotePlacement() + { + AddStep("Seek to 1935", () => EditorClock.Seek(1935)); + AddStep("Change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitobject, true)); + seekSetup(); + AddUntilStep("Wait for seeking to end", () => !EditorClock.IsSeeking); + AddAssert("Seeked to object", () => + { + return EditorClock.CurrentTimeAccurate == 2287.1875; + }); + } + + [Test] + public void TestNoSeekOnNotePlacement() + { + AddStep("Seek to 1935", () => EditorClock.Seek(1935)); + AddStep("Change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitobject, false)); + seekSetup(); + AddAssert("Not seeking", () => !EditorClock.IsSeeking); + AddAssert("Not seeked to object", () => EditorClock.CurrentTime == 1935); + } + + private void seekSetup() + { + AddStep("Seek to 1935", () => EditorClock.Seek(1935)); + AddStep("Select note", () => InputManager.Key(Key.Number2)); + AddStep("Place note", () => InputManager.MoveMouseTo(this.ChildrenOfType().First(x => x.HitObject.StartTime == 2170))); + AddStep("Click", () => InputManager.Click(MouseButton.Left)); + } } } From 025061ba66766fad6a1b3521693956c09d69592e Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sun, 19 Feb 2023 10:17:33 +0100 Subject: [PATCH 59/67] fix formating in SeekOnNote test --- .../Editor/TestScenePlacementBeforeTrackStart.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs index 4c48a361b8..142fa5ce07 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs @@ -37,20 +37,17 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public void TestSeekOnNotePlacement() { AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitobject, true)); + AddStep("Change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitObject, true)); seekSetup(); AddUntilStep("Wait for seeking to end", () => !EditorClock.IsSeeking); - AddAssert("Seeked to object", () => - { - return EditorClock.CurrentTimeAccurate == 2287.1875; - }); + AddAssert("Seeked to object", () => EditorClock.CurrentTimeAccurate == 2287.1875); } [Test] public void TestNoSeekOnNotePlacement() { AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitobject, false)); + AddStep("Change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitObject, false)); seekSetup(); AddAssert("Not seeking", () => !EditorClock.IsSeeking); AddAssert("Not seeked to object", () => EditorClock.CurrentTime == 1935); From f3522c41629ea1f4ba1cde94b4b4b1509d7074c9 Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sun, 19 Feb 2023 10:18:02 +0100 Subject: [PATCH 60/67] change bindable seekToHitObject to private --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 528088dbda..f989c81da8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; private IBindable hasTiming; - protected Bindable SeekToHitobject { get; private set; } + private Bindable seekToHitObject; protected HitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuConfigManager config) { - SeekToHitobject = config.GetBindable(OsuSetting.EditorSeekToHitobject); + seekToHitObject = config.GetBindable(OsuSetting.EditorSeekToHitObject); Config = Dependencies.Get().GetConfigFor(Ruleset); @@ -369,8 +369,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - // conditionally seek based on setting - if (!SeekToHitobject.Value) return; + if (!seekToHitObject.Value) return; if (EditorClock.CurrentTime < hitObject.StartTime) EditorClock.SeekSmoothlyTo(hitObject.StartTime); From 723a043c4351da62b8c16913b42d3e27f2e1b843 Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sun, 19 Feb 2023 10:18:22 +0100 Subject: [PATCH 61/67] naming change from Hitobject to HitObject --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/EditorStrings.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a4544200c7..9093c017d2 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -178,7 +178,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f); SetDefault(OsuSetting.EditorShowHitMarkers, true); - SetDefault(OsuSetting.EditorSeekToHitobject, true); + SetDefault(OsuSetting.EditorSeekToHitObject, true); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -375,7 +375,7 @@ namespace osu.Game.Configuration SeasonalBackgroundMode, EditorWaveformOpacity, EditorShowHitMarkers, - EditorSeekToHitobject, + EditorSeekToHitObject, DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 65cecd27d6..4557cb532f 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -20,9 +20,9 @@ namespace osu.Game.Localisation public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers"); /// - /// "Seek to Object after placement" + /// "Seek to object after placement" /// - public static LocalisableString SeekToHitobject => new TranslatableString(getKey(@"seek_to_hitobject"), @"Seek to Object after placement"); + public static LocalisableString SeekToHitObject => new TranslatableString(getKey(@"seek_to_hit_object"), @"Seek to object after placement"); /// /// "Timing" diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d6c698c139..e9a0fbf6fa 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit private Bindable editorBackgroundDim; private Bindable editorHitMarkers; - private Bindable editorSeekToHitobject; + private Bindable editorSeekToHitObject; public Editor(EditorLoader loader = null) { @@ -273,7 +273,7 @@ namespace osu.Game.Screens.Edit editorBackgroundDim = config.GetBindable(OsuSetting.EditorDim); editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers); - editorSeekToHitobject = config.GetBindable(OsuSetting.EditorSeekToHitobject); + editorSeekToHitObject = config.GetBindable(OsuSetting.EditorSeekToHitObject); AddInternal(new OsuContextMenuContainer { @@ -332,9 +332,9 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorHitMarkers }, }, - new ToggleMenuItem(EditorStrings.SeekToHitobject) + new ToggleMenuItem(EditorStrings.SeekToHitObject) { - State = { BindTarget = editorSeekToHitobject }, + State = { BindTarget = editorSeekToHitObject }, } } }, From aac32a2c9f49795e60a2f397bf833060299f2d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 13:14:51 +0100 Subject: [PATCH 62/67] Combine config and time checks into one Functionally equivalent right now, but the combined variant is more localised to what it actually needs to do, and less error-prone if any new code gets appended to the method. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f989c81da8..eb10d09560 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -369,9 +369,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - if (!seekToHitObject.Value) return; - - if (EditorClock.CurrentTime < hitObject.StartTime) + if (seekToHitObject.Value && EditorClock.CurrentTime < hitObject.StartTime) EditorClock.SeekSmoothlyTo(hitObject.StartTime); } } From 80b329f06924952df70fcb926cf2cc4fdaaf6fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 13:16:40 +0100 Subject: [PATCH 63/67] Rename test scene to match contents It does not only test "placement before track start" anymore. --- ...PlacementBeforeTrackStart.cs => TestSceneObjectPlacement.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Mania.Tests/Editor/{TestScenePlacementBeforeTrackStart.cs => TestSceneObjectPlacement.cs} (97%) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs index 142fa5ce07..02980f3ac8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs @@ -13,7 +13,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Mania.Tests.Editor { - public partial class TestScenePlacementBeforeTrackStart : EditorTestScene + public partial class TestSceneObjectPlacement : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); From 80ee917c7780e0e5da061312312430ead795f44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 13:33:06 +0100 Subject: [PATCH 64/67] Rewrite test cases - Depend less on arbitrary timings - Remove unnecessary seeks - Change method name to make more sense - Use nunit style assertions --- .../Editor/TestSceneObjectPlacement.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs index 02980f3ac8..ec2995924d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs @@ -7,7 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; -using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Input; @@ -36,29 +36,32 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestSeekOnNotePlacement() { - AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitObject, true)); - seekSetup(); - AddUntilStep("Wait for seeking to end", () => !EditorClock.IsSeeking); - AddAssert("Seeked to object", () => EditorClock.CurrentTimeAccurate == 2287.1875); + double? initialTime = null; + + AddStep("store initial time", () => initialTime = EditorClock.CurrentTime); + AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitObject, true)); + placeObject(); + AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking); + AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime)); } [Test] public void TestNoSeekOnNotePlacement() { - AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitObject, false)); - seekSetup(); - AddAssert("Not seeking", () => !EditorClock.IsSeeking); - AddAssert("Not seeked to object", () => EditorClock.CurrentTime == 1935); + double? initialTime = null; + + AddStep("store initial time", () => initialTime = EditorClock.CurrentTime); + AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitObject, false)); + placeObject(); + AddAssert("not seeking", () => !EditorClock.IsSeeking); + AddAssert("time is unchanged", () => EditorClock.CurrentTime, () => Is.EqualTo(initialTime)); } - private void seekSetup() + private void placeObject() { - AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Select note", () => InputManager.Key(Key.Number2)); - AddStep("Place note", () => InputManager.MoveMouseTo(this.ChildrenOfType().First(x => x.HitObject.StartTime == 2170))); - AddStep("Click", () => InputManager.Click(MouseButton.Left)); + AddStep("select note placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to centre of last column", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last().ScreenSpaceDrawQuad.Centre)); + AddStep("place note", () => InputManager.Click(MouseButton.Left)); } } } From 8b25598d8251b0459528ea69053b25bcb62f923b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 13:54:48 +0100 Subject: [PATCH 65/67] Rename moved test method to describe its purpose better --- .../Editor/TestSceneObjectPlacement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs index ec2995924d..6ddcabfc4d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private OsuConfigManager config { get; set; } = null!; [Test] - public void TestPlacement() + public void TestPlacementBeforeTrackStart() { AddStep("Seek to 0", () => EditorClock.Seek(0)); AddStep("Select note", () => InputManager.Key(Key.Number2)); From d9ca7102f048e1deb0ba79493866998043cde238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 15:06:40 +0100 Subject: [PATCH 66/67] Use more generic wording for future-proofing --- .../Editor/TestSceneObjectPlacement.cs | 4 ++-- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/EditorStrings.cs | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +++--- osu.Game/Screens/Edit/Editor.cs | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs index 6ddcabfc4d..13a116b209 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor double? initialTime = null; AddStep("store initial time", () => initialTime = EditorClock.CurrentTime); - AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitObject, true)); + AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, true)); placeObject(); AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking); AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime)); @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor double? initialTime = null; AddStep("store initial time", () => initialTime = EditorClock.CurrentTime); - AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitObject, false)); + AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, false)); placeObject(); AddAssert("not seeking", () => !EditorClock.IsSeeking); AddAssert("time is unchanged", () => EditorClock.CurrentTime, () => Is.EqualTo(initialTime)); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9093c017d2..70ad6bfc96 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -178,7 +178,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f); SetDefault(OsuSetting.EditorShowHitMarkers, true); - SetDefault(OsuSetting.EditorSeekToHitObject, true); + SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -375,7 +375,7 @@ namespace osu.Game.Configuration SeasonalBackgroundMode, EditorWaveformOpacity, EditorShowHitMarkers, - EditorSeekToHitObject, + EditorAutoSeekOnPlacement, DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 4557cb532f..f4e23ae7cb 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -20,9 +20,9 @@ namespace osu.Game.Localisation public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers"); /// - /// "Seek to object after placement" + /// "Automatically seek after placing objects" /// - public static LocalisableString SeekToHitObject => new TranslatableString(getKey(@"seek_to_hit_object"), @"Seek to object after placement"); + public static LocalisableString AutoSeekOnPlacement => new TranslatableString(getKey(@"auto_seek_on_placement"), @"Automatically seek after placing objects"); /// /// "Timing" diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index eb10d09560..aee86fd942 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; private IBindable hasTiming; - private Bindable seekToHitObject; + private Bindable autoSeekOnPlacement; protected HitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuConfigManager config) { - seekToHitObject = config.GetBindable(OsuSetting.EditorSeekToHitObject); + autoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement); Config = Dependencies.Get().GetConfigFor(Ruleset); @@ -369,7 +369,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - if (seekToHitObject.Value && EditorClock.CurrentTime < hitObject.StartTime) + if (autoSeekOnPlacement.Value && EditorClock.CurrentTime < hitObject.StartTime) EditorClock.SeekSmoothlyTo(hitObject.StartTime); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e9a0fbf6fa..d89392f757 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit private Bindable editorBackgroundDim; private Bindable editorHitMarkers; - private Bindable editorSeekToHitObject; + private Bindable editorAutoSeekOnPlacement; public Editor(EditorLoader loader = null) { @@ -273,7 +273,7 @@ namespace osu.Game.Screens.Edit editorBackgroundDim = config.GetBindable(OsuSetting.EditorDim); editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers); - editorSeekToHitObject = config.GetBindable(OsuSetting.EditorSeekToHitObject); + editorAutoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement); AddInternal(new OsuContextMenuContainer { @@ -332,9 +332,9 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorHitMarkers }, }, - new ToggleMenuItem(EditorStrings.SeekToHitObject) + new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) { - State = { BindTarget = editorSeekToHitObject }, + State = { BindTarget = editorAutoSeekOnPlacement }, } } }, From d7381b762ca33dffb6980f9d92e5e3eec5c269d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Feb 2023 23:52:21 +0900 Subject: [PATCH 67/67] Also tween origin position --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 2cb14814d1..f5814b4c01 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -177,6 +177,7 @@ namespace osu.Game.Overlays.SkinEditor } private Vector2? anchorPosition; + private Vector2? originPositionInDrawableSpace; protected override void Update() { @@ -186,18 +187,13 @@ namespace osu.Game.Overlays.SkinEditor return; var newAnchor = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this); - - anchorPosition ??= newAnchor; - - anchorPosition = - new Vector2( - (float)Interpolation.DampContinuously(anchorPosition.Value.X, newAnchor.X, 25, Clock.ElapsedFrameTime), - (float)Interpolation.DampContinuously(anchorPosition.Value.Y, newAnchor.Y, 25, Clock.ElapsedFrameTime) - ); - - originBox.Position = drawable.ToSpaceOfOtherDrawable(drawable.OriginPosition, this); + anchorPosition = tweenPosition(anchorPosition ?? newAnchor, newAnchor); anchorBox.Position = anchorPosition.Value; + // for the origin, tween in the drawable's local space to avoid unwanted tweening when the drawable is being dragged. + originPositionInDrawableSpace = originPositionInDrawableSpace != null ? tweenPosition(originPositionInDrawableSpace.Value, drawable.OriginPosition) : drawable.OriginPosition; + originBox.Position = drawable.ToSpaceOfOtherDrawable(originPositionInDrawableSpace.Value, this); + var point1 = ToLocalSpace(anchorBox.ScreenSpaceDrawQuad.Centre); var point2 = ToLocalSpace(originBox.ScreenSpaceDrawQuad.Centre); @@ -205,5 +201,11 @@ namespace osu.Game.Overlays.SkinEditor anchorLine.Width = (point2 - point1).Length; anchorLine.Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)); } + + private Vector2 tweenPosition(Vector2 oldPosition, Vector2 newPosition) + => new Vector2( + (float)Interpolation.DampContinuously(oldPosition.X, newPosition.X, 25, Clock.ElapsedFrameTime), + (float)Interpolation.DampContinuously(oldPosition.Y, newPosition.Y, 25, Clock.ElapsedFrameTime) + ); } }