From 22a0bc7d9d437ac1e502cd6e54c64fe66afb60c8 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 20 Dec 2023 00:05:32 +0100 Subject: [PATCH 001/579] Add basic slider distance control --- .../TestSceneSliderControlPointPiece.cs | 2 +- .../TestSceneSliderSelectionBlueprint.cs | 2 +- .../Sliders/Components/SliderTailPiece.cs | 104 ++++++++++++++++++ .../Blueprints/Sliders/SliderCircleOverlay.cs | 8 +- .../Sliders/SliderSelectionBlueprint.cs | 4 +- 5 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 99ced30ffe92..085e11460f8b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -353,7 +353,7 @@ private partial class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; - public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; + public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index d4d99e101948..adc492922779 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -198,7 +198,7 @@ private partial class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; - public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; + public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs new file mode 100644 index 000000000000..96169c5e1f2a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public partial class SliderTailPiece : SliderCircleOverlay + { + /// + /// Whether this is currently being dragged. + /// + private bool isDragging; + + private InputManager inputManager = null!; + + [Resolved(CanBeNull = true)] + private EditorBeatmap? editorBeatmap { get; set; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public SliderTailPiece(Slider slider, SliderPosition position) + : base(slider, position) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos); + + protected override bool OnHover(HoverEvent e) + { + updateCirclePieceColour(); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateCirclePieceColour(); + } + + private void updateCirclePieceColour() + { + Color4 colour = colours.Yellow; + + if (IsHovered) + colour = colour.Lighten(1); + + CirclePiece.Colour = colour; + } + + protected override bool OnDragStart(DragStartEvent e) + { + if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed) + return false; + + isDragging = true; + editorBeatmap?.BeginChange(); + + return true; + } + + protected override void OnDrag(DragEvent e) + { + double proposedDistance = Slider.Path.Distance + e.Delta.X; + + proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance); + proposedDistance = MathHelper.Clamp(proposedDistance, + 0.1 * Slider.Path.Distance / Slider.SliderVelocityMultiplier, + 10 * Slider.Path.Distance / Slider.SliderVelocityMultiplier); + + if (Precision.AlmostEquals(proposedDistance, Slider.Path.Distance)) + return; + + Slider.SliderVelocityMultiplier *= proposedDistance / Slider.Path.Distance; + Slider.Path.ExpectedDistance.Value = proposedDistance; + editorBeatmap?.Update(Slider); + } + + protected override void OnDragEnd(DragEndEvent e) + { + if (isDragging) + { + editorBeatmap?.EndChange(); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index d47cf6bf23f4..b00a42748e59 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public partial class SliderCircleOverlay : CompositeDrawable { protected readonly HitCirclePiece CirclePiece; + protected readonly Slider Slider; - private readonly Slider slider; - private readonly SliderPosition position; private readonly HitCircleOverlapMarker marker; + private readonly SliderPosition position; public SliderCircleOverlay(Slider slider, SliderPosition position) { - this.slider = slider; + Slider = slider; this.position = position; InternalChildren = new Drawable[] @@ -32,7 +32,7 @@ protected override void Update() { base.Update(); - var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle; + var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : Slider.TailCircle; CirclePiece.UpdateFrom(circle); marker.UpdateFrom(circle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index b3efe1c4955e..37c433cf8b58 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -34,7 +34,7 @@ public partial class SliderSelectionBlueprint : OsuSelectionBlueprint protected SliderBodyPiece BodyPiece { get; private set; } protected SliderCircleOverlay HeadOverlay { get; private set; } - protected SliderCircleOverlay TailOverlay { get; private set; } + protected SliderTailPiece TailPiece { get; private set; } [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } @@ -72,7 +72,7 @@ private void load() { BodyPiece = new SliderBodyPiece(), HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start), - TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End), + TailPiece = new SliderTailPiece(HitObject, SliderPosition.End), }; } From 1258a9d378a53a7f352bb5bd6fb01f0a44f3305e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 20 Dec 2023 01:48:42 +0100 Subject: [PATCH 002/579] Find closest distance value to mouse --- .../Sliders/Components/SliderTailPiece.cs | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index 96169c5e1f2a..5cf9346f2ebb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osuTK; @@ -24,6 +27,8 @@ public partial class SliderTailPiece : SliderCircleOverlay private InputManager inputManager = null!; + private readonly Cached fullPathCache = new Cached(); + [Resolved(CanBeNull = true)] private EditorBeatmap? editorBeatmap { get; set; } @@ -33,6 +38,7 @@ public partial class SliderTailPiece : SliderCircleOverlay public SliderTailPiece(Slider slider, SliderPosition position) : base(slider, position) { + Slider.Path.ControlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate(); } protected override void LoadComplete() @@ -78,17 +84,18 @@ protected override bool OnDragStart(DragStartEvent e) protected override void OnDrag(DragEvent e) { - double proposedDistance = Slider.Path.Distance + e.Delta.X; + double oldDistance = Slider.Path.Distance; + double proposedDistance = findClosestPathDistance(e); proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance); proposedDistance = MathHelper.Clamp(proposedDistance, - 0.1 * Slider.Path.Distance / Slider.SliderVelocityMultiplier, - 10 * Slider.Path.Distance / Slider.SliderVelocityMultiplier); + 0.1 * oldDistance / Slider.SliderVelocityMultiplier, + 10 * oldDistance / Slider.SliderVelocityMultiplier); - if (Precision.AlmostEquals(proposedDistance, Slider.Path.Distance)) + if (Precision.AlmostEquals(proposedDistance, oldDistance)) return; - Slider.SliderVelocityMultiplier *= proposedDistance / Slider.Path.Distance; + Slider.SliderVelocityMultiplier *= proposedDistance / oldDistance; Slider.Path.ExpectedDistance.Value = proposedDistance; editorBeatmap?.Update(Slider); } @@ -100,5 +107,48 @@ protected override void OnDragEnd(DragEndEvent e) editorBeatmap?.EndChange(); } } + + /// + /// Finds the expected distance value for which the slider end is closest to the mouse position. + /// + private double findClosestPathDistance(DragEvent e) + { + const double step1 = 10; + const double step2 = 0.1; + + var desiredPosition = e.MousePosition - Slider.Position; + + if (!fullPathCache.IsValid) + fullPathCache.Value = new SliderPath(Slider.Path.ControlPoints.ToArray()); + + // Do a linear search to find the closest point on the path to the mouse position. + double bestValue = 0; + double minDistance = double.MaxValue; + + for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1) + { + double t = d / fullPathCache.Value.CalculatedDistance; + float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition); + + if (dist >= minDistance) continue; + + minDistance = dist; + bestValue = d; + } + + // Do another linear search to fine-tune the result. + for (double d = bestValue - step1; d <= bestValue + step1; d += step2) + { + double t = d / fullPathCache.Value.CalculatedDistance; + float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition); + + if (dist >= minDistance) continue; + + minDistance = dist; + bestValue = d; + } + + return bestValue; + } } } From 1365a1b7bec8d444dfe99f098ed2ad2c1863f770 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 20 Dec 2023 01:57:11 +0100 Subject: [PATCH 003/579] fix tests --- .../Editor/TestSceneSliderControlPointPiece.cs | 13 ++++++++++++- .../Editor/TestSceneSliderSelectionBlueprint.cs | 15 +++++++++++++-- .../Sliders/SliderSelectionBlueprint.cs | 3 ++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 085e11460f8b..1a7430704dd1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -353,7 +353,7 @@ private partial class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; - public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece; + public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) @@ -362,6 +362,7 @@ public TestSliderBlueprint(Slider slider) } protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position); + protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position); } private partial class TestSliderCircleOverlay : SliderCircleOverlay @@ -373,5 +374,15 @@ public TestSliderCircleOverlay(Slider slider, SliderPosition position) { } } + + private partial class TestSliderTailPiece : SliderTailPiece + { + public new HitCirclePiece CirclePiece => base.CirclePiece; + + public TestSliderTailPiece(Slider slider, SliderPosition position) + : base(slider, position) + { + } + } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index adc492922779..2c5cff3f703c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -179,7 +179,7 @@ private void checkPositions() () => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); AddAssert("tail positioned correctly", - () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); + () => Precision.AlmostEquals(blueprint.TailPiece.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); } private void moveMouseToControlPoint(int index) @@ -198,7 +198,7 @@ private partial class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; - public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece; + public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) @@ -207,6 +207,7 @@ public TestSliderBlueprint(Slider slider) } protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position); + protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position); } private partial class TestSliderCircleOverlay : SliderCircleOverlay @@ -218,5 +219,15 @@ public TestSliderCircleOverlay(Slider slider, SliderPosition position) { } } + + private partial class TestSliderTailPiece : SliderTailPiece + { + public new HitCirclePiece CirclePiece => base.CirclePiece; + + public TestSliderTailPiece(Slider slider, SliderPosition position) + : base(slider, position) + { + } + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 37c433cf8b58..a13eeb02085d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -72,7 +72,7 @@ private void load() { BodyPiece = new SliderBodyPiece(), HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start), - TailPiece = new SliderTailPiece(HitObject, SliderPosition.End), + TailPiece = CreateTailPiece(HitObject, SliderPosition.End), }; } @@ -415,5 +415,6 @@ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position); + protected virtual SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new SliderTailPiece(slider, position); } } From 3aaf0b39f56e5d3df4a597a5925258470fbb73c3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 20 Dec 2023 02:12:16 +0100 Subject: [PATCH 004/579] Add slider tail dragging test --- .../TestSceneSliderSelectionBlueprint.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 2c5cff3f703c..3f9620a8d1d4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -163,6 +163,54 @@ public void TestMultipleControlPointDeselectionViaClickOutside() checkControlPointSelected(1, false); } + [Test] + public void TestDragSliderTail() + { + AddStep($"move mouse to slider tail", () => + { + Vector2 position = slider.EndPosition + new Vector2(10, 0); + InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); + }); + AddStep("shift + drag", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.PressButton(MouseButton.Left); + }); + moveMouseToControlPoint(1); + AddStep("release", () => + { + InputManager.ReleaseButton(MouseButton.Left); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddAssert("expected distance halved", + () => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1)); + + AddStep($"move mouse to slider tail", () => + { + Vector2 position = slider.EndPosition + new Vector2(10, 0); + InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); + }); + AddStep("shift + drag", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.PressButton(MouseButton.Left); + }); + AddStep($"move mouse beyond last control point", () => + { + Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0); + InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); + }); + AddStep("release", () => + { + InputManager.ReleaseButton(MouseButton.Left); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddAssert("expected distance is calculated distance", + () => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1)); + } + private void moveHitObject() { AddStep("move hitobject", () => From 66f4dcc578b3f760e3d2e6935fca2fecfec526a7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 20 Dec 2023 02:32:20 +0100 Subject: [PATCH 005/579] fix repeat sliders half --- .../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index 5cf9346f2ebb..e60e41f6d592 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -65,7 +65,7 @@ private void updateCirclePieceColour() { Color4 colour = colours.Yellow; - if (IsHovered) + if (IsHovered && Slider.RepeatCount % 2 == 0) colour = colour.Lighten(1); CirclePiece.Colour = colour; @@ -73,7 +73,8 @@ private void updateCirclePieceColour() protected override bool OnDragStart(DragStartEvent e) { - if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed) + // Disable dragging if the slider has an uneven number of repeats because the slider tail will be on the wrong side of the path. + if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed || Slider.RepeatCount % 2 == 1) return false; isDragging = true; From f7cb6b9ed09bd041f3c21448abcb83c8de3ed09e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 20 Dec 2023 12:58:32 +0100 Subject: [PATCH 006/579] Fix all repeat sliders being draggable --- .../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 5 ++--- .../Edit/Blueprints/Sliders/SliderCircleOverlay.cs | 3 ++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index e60e41f6d592..5cf9346f2ebb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -65,7 +65,7 @@ private void updateCirclePieceColour() { Color4 colour = colours.Yellow; - if (IsHovered && Slider.RepeatCount % 2 == 0) + if (IsHovered) colour = colour.Lighten(1); CirclePiece.Colour = colour; @@ -73,8 +73,7 @@ private void updateCirclePieceColour() protected override bool OnDragStart(DragStartEvent e) { - // Disable dragging if the slider has an uneven number of repeats because the slider tail will be on the wrong side of the path. - if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed || Slider.RepeatCount % 2 == 1) + if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed) return false; isDragging = true; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index b00a42748e59..2bf5118039a1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -32,7 +32,8 @@ protected override void Update() { base.Update(); - var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : Slider.TailCircle; + var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : + Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat; CirclePiece.UpdateFrom(circle); marker.UpdateFrom(circle); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 506145568ecd..7a22bf5c4d6d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -162,6 +162,9 @@ public double SliderVelocityMultiplier [JsonIgnore] public SliderTailCircle TailCircle { get; protected set; } + [JsonIgnore] + public SliderRepeat LastRepeat { get; protected set; } + public Slider() { SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples(); @@ -225,7 +228,7 @@ protected override void CreateNestedHitObjects(CancellationToken cancellationTok break; case SliderEventType.Repeat: - AddNested(new SliderRepeat(this) + AddNested(LastRepeat = new SliderRepeat(this) { RepeatIndex = e.SpanIndex, StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, @@ -248,6 +251,9 @@ private void updateNestedPositions() if (TailCircle != null) TailCircle.Position = EndPosition; + + if (LastRepeat != null) + LastRepeat.Position = RepeatCount % 2 == 0 ? Position : Position + Path.PositionAt(1); } protected void UpdateNestedSamples() From d000da725df5d5a2e031c96ce1861d581feac497 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 20 Dec 2023 13:14:05 +0100 Subject: [PATCH 007/579] fix code quality --- .../Editor/TestSceneSliderSelectionBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 3f9620a8d1d4..3faf18146518 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -166,7 +166,7 @@ public void TestMultipleControlPointDeselectionViaClickOutside() [Test] public void TestDragSliderTail() { - AddStep($"move mouse to slider tail", () => + AddStep("move mouse to slider tail", () => { Vector2 position = slider.EndPosition + new Vector2(10, 0); InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); @@ -186,7 +186,7 @@ public void TestDragSliderTail() AddAssert("expected distance halved", () => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1)); - AddStep($"move mouse to slider tail", () => + AddStep("move mouse to slider tail", () => { Vector2 position = slider.EndPosition + new Vector2(10, 0); InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); @@ -196,7 +196,7 @@ public void TestDragSliderTail() InputManager.PressKey(Key.ShiftLeft); InputManager.PressButton(MouseButton.Left); }); - AddStep($"move mouse beyond last control point", () => + AddStep("move mouse beyond last control point", () => { Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0); InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); From eedb436389afe01b1666d6e3ed73d10cc8b2d070 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 03:47:42 +0300 Subject: [PATCH 008/579] Move combo counter to ruleset-specific HUD components target --- .../Argon/CatchArgonSkinTransformer.cs | 2 +- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 2 +- .../Argon/TaikoArgonSkinTransformer.cs | 8 +-- osu.Game/Rulesets/Ruleset.cs | 14 ++++- osu.Game/Skinning/ArgonSkin.cs | 16 +----- osu.Game/Skinning/ArgonSkinTransformer.cs | 53 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 1 - osu.Game/Skinning/LegacySkinTransformer.cs | 44 +++++++++++++-- 8 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Skinning/ArgonSkinTransformer.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs index 520c2de2480a..a67945df98eb 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs @@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Argon { - public class CatchArgonSkinTransformer : SkinTransformer + public class CatchArgonSkinTransformer : ArgonSkinTransformer { public CatchArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 0f9c97059c40..9526ea05c965 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public class OsuArgonSkinTransformer : SkinTransformer + public class OsuArgonSkinTransformer : ArgonSkinTransformer { public OsuArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs index 9fcecd2b1af6..7d38d6c9e5d0 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs @@ -7,16 +7,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon { - public class TaikoArgonSkinTransformer : SkinTransformer + public class TaikoArgonSkinTransformer : ArgonSkinTransformer { public TaikoArgonSkinTransformer(ISkin skin) : base(skin) { } - public override Drawable? GetDrawableComponent(ISkinComponentLookup component) + public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - switch (component) + switch (lookup) { case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. @@ -75,7 +75,7 @@ public TaikoArgonSkinTransformer(ISkin skin) break; } - return base.GetDrawableComponent(component); + return base.GetDrawableComponent(lookup); } } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 37a35fd3ae6c..c7d4779064d4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -212,7 +212,19 @@ public virtual LegacyMods ConvertToLegacyMods(Mod[] mods) /// The source skin. /// The current beatmap. /// A skin with a transformer applied, or null if no transformation is provided by this ruleset. - public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => null; + public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) + { + switch (skin) + { + case LegacySkin: + return new LegacySkinTransformer(skin); + + case ArgonSkin: + return new ArgonSkinTransformer(skin); + } + + return null; + } protected Ruleset() { diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6fcab6a9771e..bdb65713a054 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -111,14 +111,13 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) return songSelectComponents; case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => + var mainHUDComponents = new DefaultSkinComponentsContainer(container => { var health = container.OfType().FirstOrDefault(); var healthLine = container.OfType().FirstOrDefault(); var wedgePieces = container.OfType().ToArray(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); - var combo = container.OfType().FirstOrDefault(); var songProgress = container.OfType().FirstOrDefault(); var keyCounter = container.OfType().FirstOrDefault(); @@ -192,13 +191,6 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) keyCounter.Origin = Anchor.BottomRight; keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height)); } - - if (combo != null && hitError != null) - { - combo.Anchor = Anchor.BottomLeft; - combo.Origin = Anchor.BottomLeft; - combo.Position = new Vector2((hitError.Width + padding), -(padding * 2 + song_progress_offset_height)); - } } } }) @@ -224,10 +216,6 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) CornerRadius = { Value = 0.5f } }, new ArgonAccuracyCounter(), - new ArgonComboCounter - { - Scale = new Vector2(1.3f) - }, new BarHitErrorMeter(), new BarHitErrorMeter(), new ArgonSongProgress(), @@ -235,7 +223,7 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) } }; - return skinnableTargetWrapper; + return mainHUDComponents; } return null; diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs new file mode 100644 index 000000000000..387a7a9c0b18 --- /dev/null +++ b/osu.Game/Skinning/ArgonSkinTransformer.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Skinning +{ + public class ArgonSkinTransformer : SkinTransformer + { + public ArgonSkinTransformer(ISkin skin) + : base(skin) + { + } + + public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) + { + switch (lookup) + { + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); + + rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => + { + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Position = new Vector2(36, -66); + combo.Scale = new Vector2(1.3f); + } + }) + { + new ArgonComboCounter(), + }; + + return rulesetHUDComponents; + } + + break; + } + + return base.GetDrawableComponent(lookup); + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 8f0cd59b68c2..b8e721165eb2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -399,7 +399,6 @@ protected override void ParseConfigurationStream(Stream stream) { Children = new Drawable[] { - new LegacyComboCounter(), new LegacyScoreCounter(), new LegacyAccuracyCounter(), new LegacySongProgress(), diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 367e5bae0178..3ea316c0c794 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -1,28 +1,62 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; +using osuTK; using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning { - /// - /// Transformer used to handle support of legacy features for individual rulesets. - /// - public abstract class LegacySkinTransformer : SkinTransformer + public class LegacySkinTransformer : SkinTransformer { /// /// Whether the skin being transformed is able to provide legacy resources for the ruleset. /// public virtual bool IsProvidingLegacyResources => this.HasFont(LegacyFont.Combo); - protected LegacySkinTransformer(ISkin skin) + public LegacySkinTransformer(ISkin skin) : base(skin) { } + public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) + { + switch (lookup) + { + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + var rulesetHUDComponents = base.GetDrawableComponent(lookup); + + rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => + { + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); + } + }) + { + new LegacyComboCounter() + }; + + return rulesetHUDComponents; + } + + break; + } + + return base.GetDrawableComponent(lookup); + } + public override ISample? GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) From e469e06271e4e4b23a93b5d0c30bf7693fb947e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 03:54:53 +0300 Subject: [PATCH 009/579] Refactor `CatchLegacySkinTransformer` logic and remove `HiddenByRulesetImplementation` entirely --- .../TestSceneCatchPlayerLegacySkin.cs | 8 +- .../Legacy/CatchLegacySkinTransformer.cs | 104 ++++++++---------- .../TestSceneSkinnableComboCounter.cs | 13 --- osu.Game/Skinning/LegacyComboCounter.cs | 12 -- 4 files changed, 50 insertions(+), 87 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 540623035905..99325e14c8a5 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Skinning; @@ -19,7 +18,7 @@ public partial class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); [Test] - public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin) + public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin) { if (withModifiedSkin) { @@ -29,10 +28,7 @@ public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin) CreateTest(); } - AddAssert("legacy HUD combo counter hidden", () => - { - return Player.ChildrenOfType().All(c => c.ChildrenOfType().Single().Alpha == 0f); - }); + AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any()); } } } diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index fb8af9bdb69b..675c61a2c565 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Skinning; using osuTK.Graphics; @@ -28,76 +27,69 @@ public CatchLegacySkinTransformer(ISkin skin) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is SkinComponentsContainerLookup containerLookup) + switch (lookup) { - switch (containerLookup.Target) - { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - var components = base.GetDrawableComponent(lookup) as Container; - - if (providesComboCounter && components != null) - { - // catch may provide its own combo counter; hide the default. - // todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed. - foreach (var legacyComboCounter in components.OfType()) - legacyComboCounter.HiddenByRulesetImplementation = false; - } - - return components; - } - } + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + Debug.Assert(containerLookup.Ruleset.ShortName == CatchRuleset.SHORT_NAME); + // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. + return Skin.GetDrawableComponent(lookup); + } - if (lookup is CatchSkinComponentLookup catchSkinComponent) - { - switch (catchSkinComponent.Component) - { - case CatchSkinComponents.Fruit: - if (hasPear) - return new LegacyFruitPiece(); + break; - return null; + case CatchSkinComponentLookup catchSkinComponent: + switch (catchSkinComponent.Component) + { + case CatchSkinComponents.Fruit: + if (hasPear) + return new LegacyFruitPiece(); - case CatchSkinComponents.Banana: - if (GetTexture("fruit-bananas") != null) - return new LegacyBananaPiece(); + return null; - return null; + case CatchSkinComponents.Banana: + if (GetTexture("fruit-bananas") != null) + return new LegacyBananaPiece(); - case CatchSkinComponents.Droplet: - if (GetTexture("fruit-drop") != null) - return new LegacyDropletPiece(); + return null; - return null; + case CatchSkinComponents.Droplet: + if (GetTexture("fruit-drop") != null) + return new LegacyDropletPiece(); - case CatchSkinComponents.Catcher: - decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; + return null; - if (version < 2.3m) - { - if (hasOldStyleCatcherSprite()) - return new LegacyCatcherOld(); - } + case CatchSkinComponents.Catcher: + decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; - if (hasNewStyleCatcherSprite()) - return new LegacyCatcherNew(); + if (version < 2.3m) + { + if (hasOldStyleCatcherSprite()) + return new LegacyCatcherOld(); + } - return null; + if (hasNewStyleCatcherSprite()) + return new LegacyCatcherNew(); - case CatchSkinComponents.CatchComboCounter: - if (providesComboCounter) - return new LegacyCatchComboCounter(); + return null; - return null; + case CatchSkinComponents.CatchComboCounter: + if (providesComboCounter) + return new LegacyCatchComboCounter(); - case CatchSkinComponents.HitExplosion: - if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite()) - return new LegacyHitExplosion(); + return null; - return null; + case CatchSkinComponents.HitExplosion: + if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite()) + return new LegacyHitExplosion(); + + return null; - default: - throw new UnsupportedSkinComponentException(lookup); - } + default: + throw new UnsupportedSkinComponentException(lookup); + } } return base.GetDrawableComponent(lookup); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs index 72f40d9c6f8e..a15a3197c578 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -28,17 +27,5 @@ public void TestComboCounterIncrementing() AddStep("reset combo", () => scoreProcessor.Combo.Value = 0); } - - [Test] - public void TestLegacyComboCounterHiddenByRulesetImplementation() - { - AddToggleStep("toggle legacy hidden by ruleset", visible => - { - foreach (var legacyCounter in this.ChildrenOfType()) - legacyCounter.HiddenByRulesetImplementation = visible; - }); - - AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10); - } } } diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs index cd72055fce66..d77a39f6074e 100644 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -43,18 +43,6 @@ public partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawab private readonly Container counterContainer; - /// - /// 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, - /// without potentially affecting the user's selected skin. - /// - public bool HiddenByRulesetImplementation - { - set => counterContainer.Alpha = value ? 1 : 0; - } - public bool UsesFixedAnchor { get; set; } public LegacyComboCounter() From 78cb6b68518651a568c00c3434b9695ec76f6c53 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:29:18 +0300 Subject: [PATCH 010/579] Abstractify `LegacyComboCounter` to re-use for mania --- .../TestSceneCatchPlayerLegacySkin.cs | 2 +- .../TestSceneSkinnableComboCounter.cs | 2 +- osu.Game/Skinning/LegacyComboCounter.cs | 163 +++++------------- .../Skinning/LegacyDefaultComboCounter.cs | 85 +++++++++ osu.Game/Skinning/LegacySkinTransformer.cs | 4 +- osu.Game/Skinning/Skin.cs | 1 + 6 files changed, 134 insertions(+), 123 deletions(-) create mode 100644 osu.Game/Skinning/LegacyDefaultComboCounter.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 99325e14c8a5..7812e02a6363 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -28,7 +28,7 @@ public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin) CreateTest(); } - AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any()); + AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any()); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs index a15a3197c578..a6196a8ca012 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs @@ -18,7 +18,7 @@ public partial class TestSceneSkinnableComboCounter : SkinnableHUDComponentTestS protected override Drawable CreateArgonImplementation() => new ArgonComboCounter(); protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter(); - protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter(); + protected override Drawable CreateLegacyImplementation() => new LegacyDefaultComboCounter(); [Test] public void TestComboCounterIncrementing() diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs index d77a39f6074e..7003e0d3c83a 100644 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -5,25 +5,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; -using osuTK; namespace osu.Game.Skinning { /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable + public abstract partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable { public Bindable Current { get; } = new BindableInt { MinValue = 0 }; - private uint scheduledPopOutCurrentId; - - private const double big_pop_out_duration = 300; - - private const double small_pop_out_duration = 100; - private const double fade_out_duration = 100; /// @@ -31,9 +23,8 @@ public partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawab /// private const double rolling_duration = 20; - private readonly Drawable popOutCount; - - private readonly Drawable displayedCountSpriteText; + protected readonly LegacySpriteText PopOutCountText; + protected readonly LegacySpriteText DisplayedCountText; private int previousValue; @@ -45,17 +36,10 @@ public partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawab public bool UsesFixedAnchor { get; set; } - public LegacyComboCounter() + protected LegacyComboCounter() { AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - - Margin = new MarginPadding(10); - - Scale = new Vector2(1.28f); - InternalChildren = new[] { counterContainer = new Container @@ -63,18 +47,16 @@ public LegacyComboCounter() AlwaysPresent = true, Children = new[] { - popOutCount = new LegacySpriteText(LegacyFont.Combo) + PopOutCountText = new LegacySpriteText(LegacyFont.Combo) { Alpha = 0, Blending = BlendingParameters.Additive, - Anchor = Anchor.BottomLeft, BypassAutoSizeAxes = Axes.Both, }, - displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) + DisplayedCountText = new LegacySpriteText(LegacyFont.Combo) { Alpha = 0, AlwaysPresent = true, - Anchor = Anchor.BottomLeft, BypassAutoSizeAxes = Axes.Both, }, } @@ -114,26 +96,12 @@ protected override void LoadComplete() { base.LoadComplete(); - ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value); - ((IHasText)popOutCount).Text = formatCount(Current.Value); + DisplayedCountText.Text = FormatCount(Current.Value); + PopOutCountText.Text = FormatCount(Current.Value); Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); - updateLayout(); - } - - private void updateLayout() - { - const float font_height_ratio = 0.625f; - const float vertical_offset = 9; - - displayedCountSpriteText.OriginPosition = new Vector2(0, font_height_ratio * displayedCountSpriteText.Height + vertical_offset); - displayedCountSpriteText.Position = new Vector2(0, -(1 - font_height_ratio) * displayedCountSpriteText.Height + vertical_offset); - - popOutCount.OriginPosition = new Vector2(3, font_height_ratio * popOutCount.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left - popOutCount.Position = new Vector2(0, -(1 - font_height_ratio) * popOutCount.Height + vertical_offset); - - counterContainer.Size = displayedCountSpriteText.Size; + counterContainer.Size = DisplayedCountText.Size; } private void updateCount(bool rolling) @@ -147,127 +115,84 @@ private void updateCount(bool rolling) if (!rolling) { FinishTransforms(false, nameof(DisplayedCount)); + isRolling = false; DisplayedCount = prev; if (prev + 1 == Current.Value) - onCountIncrement(prev, Current.Value); + OnCountIncrement(); else - onCountChange(Current.Value); + OnCountChange(); } else { - onCountRolling(displayedCount, Current.Value); + OnCountRolling(); isRolling = true; } } - private void transformPopOut(int newValue) - { - ((IHasText)popOutCount).Text = formatCount(newValue); - - popOutCount.ScaleTo(1.56f) - .ScaleTo(1, big_pop_out_duration); - - popOutCount.FadeTo(0.6f) - .FadeOut(big_pop_out_duration); - } - - private void transformNoPopOut(int newValue) - { - ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); - - counterContainer.Size = displayedCountSpriteText.Size; - - displayedCountSpriteText.ScaleTo(1); - } - - private void transformPopOutSmall(int newValue) - { - ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); - - counterContainer.Size = displayedCountSpriteText.Size; - - displayedCountSpriteText.ScaleTo(1).Then() - .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then() - .ScaleTo(1, small_pop_out_duration / 2, Easing.Out); - } - - private void scheduledPopOutSmall(uint id) - { - // Too late; scheduled task invalidated - if (id != scheduledPopOutCurrentId) - return; - - DisplayedCount++; - } - - private void onCountIncrement(int currentValue, int newValue) + /// + /// Raised when the counter should display the new value with transitions. + /// + protected virtual void OnCountIncrement() { - scheduledPopOutCurrentId++; - - if (DisplayedCount < currentValue) + if (DisplayedCount < Current.Value - 1) DisplayedCount++; - displayedCountSpriteText.Show(); - - transformPopOut(newValue); - - uint newTaskId = scheduledPopOutCurrentId; - - Scheduler.AddDelayed(delegate - { - scheduledPopOutSmall(newTaskId); - }, big_pop_out_duration - 140); + DisplayedCount++; } - private void onCountRolling(int currentValue, int newValue) + /// + /// Raised when the counter should roll to the new combo value (usually roll back to zero). + /// + protected virtual void OnCountRolling() { - scheduledPopOutCurrentId++; - // Hides displayed count if was increasing from 0 to 1 but didn't finish - if (currentValue == 0 && newValue == 0) - displayedCountSpriteText.FadeOut(fade_out_duration); + if (DisplayedCount == 0 && Current.Value == 0) + DisplayedCountText.FadeOut(fade_out_duration); - transformRoll(currentValue, newValue); + transformRoll(DisplayedCount, Current.Value); } - private void onCountChange(int newValue) + /// + /// Raised when the counter should display the new combo value without any transitions. + /// + protected virtual void OnCountChange() { - scheduledPopOutCurrentId++; - - if (newValue == 0) - displayedCountSpriteText.FadeOut(); + if (Current.Value == 0) + DisplayedCountText.FadeOut(); - DisplayedCount = newValue; + DisplayedCount = Current.Value; } private void onDisplayedCountRolling(int newValue) { if (newValue == 0) - displayedCountSpriteText.FadeOut(fade_out_duration); - else - displayedCountSpriteText.Show(); + DisplayedCountText.FadeOut(fade_out_duration); - transformNoPopOut(newValue); + DisplayedCountText.Text = FormatCount(newValue); + counterContainer.Size = DisplayedCountText.Size; } private void onDisplayedCountChange(int newValue) { - displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); - transformNoPopOut(newValue); + DisplayedCountText.FadeTo(newValue == 0 ? 0 : 1); + DisplayedCountText.Text = FormatCount(newValue); + + counterContainer.Size = DisplayedCountText.Size; } private void onDisplayedCountIncrement(int newValue) { - displayedCountSpriteText.Show(); - transformPopOutSmall(newValue); + DisplayedCountText.Text = FormatCount(newValue); + + counterContainer.Size = DisplayedCountText.Size; } private void transformRoll(int currentValue, int newValue) => this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue)); - private string formatCount(int count) => $@"{count}x"; + protected virtual string FormatCount(int count) => $@"{count}"; private double getProportionalDuration(int currentValue, int newValue) { diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs new file mode 100644 index 000000000000..f6333589934e --- /dev/null +++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Threading; +using osuTK; + +namespace osu.Game.Skinning +{ + /// + /// Uses the 'x' symbol and has a pop-out effect while rolling over. + /// + public partial class LegacyDefaultComboCounter : LegacyComboCounter + { + private const double big_pop_out_duration = 300; + private const double small_pop_out_duration = 100; + + private ScheduledDelegate? scheduledPopOut; + + public LegacyDefaultComboCounter() + { + Margin = new MarginPadding(10); + + PopOutCountText.Anchor = Anchor.BottomLeft; + DisplayedCountText.Anchor = Anchor.BottomLeft; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + const float font_height_ratio = 0.625f; + const float vertical_offset = 9; + + DisplayedCountText.OriginPosition = new Vector2(0, font_height_ratio * DisplayedCountText.Height + vertical_offset); + DisplayedCountText.Position = new Vector2(0, -(1 - font_height_ratio) * DisplayedCountText.Height + vertical_offset); + + PopOutCountText.OriginPosition = new Vector2(3, font_height_ratio * PopOutCountText.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left + PopOutCountText.Position = new Vector2(0, -(1 - font_height_ratio) * PopOutCountText.Height + vertical_offset); + } + + protected override void OnCountIncrement() + { + scheduledPopOut?.Cancel(); + scheduledPopOut = null; + + DisplayedCountText.Show(); + + PopOutCountText.Text = FormatCount(Current.Value); + + PopOutCountText.ScaleTo(1.56f) + .ScaleTo(1, big_pop_out_duration); + + PopOutCountText.FadeTo(0.6f) + .FadeOut(big_pop_out_duration); + + this.Delay(big_pop_out_duration - 140).Schedule(() => + { + base.OnCountIncrement(); + + DisplayedCountText.ScaleTo(1).Then() + .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then() + .ScaleTo(1, small_pop_out_duration / 2, Easing.Out); + }, out scheduledPopOut); + } + + protected override void OnCountRolling() + { + scheduledPopOut?.Cancel(); + scheduledPopOut = null; + + base.OnCountRolling(); + } + + protected override void OnCountChange() + { + scheduledPopOut?.Cancel(); + scheduledPopOut = null; + + base.OnCountChange(); + } + + protected override string FormatCount(int count) => $@"{count}x"; + } +} diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 3ea316c0c794..66978fc6b06b 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -35,7 +35,7 @@ public LegacySkinTransformer(ISkin skin) rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => { - var combo = container.OfType().FirstOrDefault(); + var combo = container.OfType().FirstOrDefault(); if (combo != null) { @@ -45,7 +45,7 @@ public LegacySkinTransformer(ISkin skin) } }) { - new LegacyComboCounter() + new LegacyDefaultComboCounter() }; return rulesetHUDComponents; diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 9ee69d033d73..80bb34010910 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -153,6 +153,7 @@ protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStor // handle namespace changes... 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"); + jsonContent = jsonContent.Replace(@"osu.Game.Skinning.LegacyComboCounter", @"osu.Game.Skinning.LegacyDefaultComboCounter"); var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); From ece532b837a3ebe76791e8fc5c528cba854a5ad0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:18:56 +0300 Subject: [PATCH 011/579] Add legacy mania combo counter lookups --- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 1 + osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++ osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 6 ++++++ 4 files changed, 13 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 042836984a4e..db1f216b6e47 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -39,6 +39,7 @@ public class LegacyManiaSkinConfiguration : IHasCustomColours public float HitPosition = DEFAULT_HIT_POSITION; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; + public float ComboPosition = 111 * POSITION_SCALE_FACTOR; public float ScorePosition = 300 * POSITION_SCALE_FACTOR; public bool ShowJudgementLine = true; public bool KeysUnderNotes; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index cacca0de23b8..fc90fc89ebfd 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -42,6 +42,7 @@ public enum LegacyManiaSkinConfigurationLookups LeftLineWidth, RightLineWidth, HitPosition, + ComboPosition, ScorePosition, LightPosition, StagePaddingTop, @@ -63,6 +64,7 @@ public enum LegacyManiaSkinConfigurationLookups JudgementLineColour, ColumnBackgroundColour, ColumnLightColour, + ComboBreakColour, MinimumColumnWidth, LeftStageImage, RightStageImage, diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index b472afb74f6c..5dd8f9c52dd0 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -94,6 +94,10 @@ private void flushPendingLines() currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; break; + case "ComboPosition": + currentConfig.ComboPosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + break; + case "ScorePosition": currentConfig.ScorePosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b8e721165eb2..848a6366edb6 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -152,6 +152,9 @@ protected override void ParseConfigurationStream(Stream stream) case LegacyManiaSkinConfigurationLookups.HitPosition: return SkinUtils.As(new Bindable(existing.HitPosition)); + case LegacyManiaSkinConfigurationLookups.ComboPosition: + return SkinUtils.As(new Bindable(existing.ComboPosition)); + case LegacyManiaSkinConfigurationLookups.ScorePosition: return SkinUtils.As(new Bindable(existing.ScorePosition)); @@ -189,6 +192,9 @@ protected override void ParseConfigurationStream(Stream stream) Debug.Assert(maniaLookup.ColumnIndex != null); return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.ColumnIndex + 1}")); + case LegacyManiaSkinConfigurationLookups.ComboBreakColour: + return SkinUtils.As(getCustomColour(existing, "ColourBreak")); + case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth: return SkinUtils.As(new Bindable(existing.MinimumColumnWidth)); From 8be3f4f632f62d70c365c45c1f48a1747b4579f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:20:44 +0300 Subject: [PATCH 012/579] Add legacy mania combo counter implementation --- .../Legacy/LegacyManiaComboCounter.cs | 94 +++++++++++++++++++ .../Legacy/ManiaLegacySkinTransformer.cs | 31 ++++++ 2 files changed, 125 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs new file mode 100644 index 000000000000..fd309f625046 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Legacy +{ + public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable + { + private DrawableManiaRuleset maniaRuleset = null!; + + bool ISerialisableDrawable.SupportsClosestAnchor => false; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, DrawableRuleset ruleset) + { + maniaRuleset = (DrawableManiaRuleset)ruleset; + + DisplayedCountText.Anchor = Anchor.Centre; + DisplayedCountText.Origin = Anchor.Centre; + + PopOutCountText.Anchor = Anchor.Centre; + PopOutCountText.Origin = Anchor.Centre; + PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; + + UsesFixedAnchor = true; + } + + private IBindable direction = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy(); + direction.BindValueChanged(_ => updateAnchor()); + + // two schedules are required so that updateAnchor is executed in the next frame, + // which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer. + Schedule(() => Schedule(updateAnchor)); + } + + private void updateAnchor() + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + + // since we flip the vertical anchor when changing scroll direction, + // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. + if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) + Y = -Y; + } + + protected override void OnCountIncrement() + { + base.OnCountIncrement(); + + PopOutCountText.Hide(); + DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f)) + .ScaleTo(new Vector2(1f), 300, Easing.Out) + .FadeIn(120); + } + + protected override void OnCountChange() + { + base.OnCountChange(); + + PopOutCountText.Hide(); + DisplayedCountText.ScaleTo(1f); + } + + protected override void OnCountRolling() + { + if (DisplayedCount > 0) + { + PopOutCountText.Text = FormatCount(DisplayedCount); + PopOutCountText.FadeTo(0.8f).FadeOut(200) + .ScaleTo(1f).ScaleTo(4f, 200); + + DisplayedCountText.FadeTo(0.5f, 300); + } + + base.OnCountRolling(); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 73c521b2edde..c539c239bdbd 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -5,9 +5,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; @@ -78,6 +81,34 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) { switch (lookup) { + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME); + + var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); + + rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; + } + }) + { + new LegacyManiaComboCounter(), + }; + + return rulesetHUDComponents; + } + + break; + case GameplaySkinComponentLookup resultComponent: return getResult(resultComponent.Component); From 408287e086b18187c72e0bde57571be39fd621b0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:20:51 +0300 Subject: [PATCH 013/579] Add very basic argon mania combo counter implementation --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 84 +++++++++++++++++++ .../Argon/ManiaArgonSkinTransformer.cs | 33 +++++++- 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs new file mode 100644 index 000000000000..1c8e43345abf --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public partial class ArgonManiaComboCounter : ComboCounter, ISerialisableDrawable + { + private OsuSpriteText text = null!; + + protected override double RollingDuration => 500; + protected override Easing RollingEasing => Easing.OutQuint; + + private DrawableManiaRuleset maniaRuleset = null!; + + bool ISerialisableDrawable.SupportsClosestAnchor => false; + + [BackgroundDependencyLoader] + private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor) + { + maniaRuleset = (DrawableManiaRuleset)ruleset; + + Current.BindTo(scoreProcessor.Combo); + Current.BindValueChanged(combo => + { + if (combo.OldValue == 0 && combo.NewValue > 0) + text.FadeIn(200, Easing.OutQuint); + else if (combo.OldValue > 0 && combo.NewValue == 0) + { + if (combo.OldValue > 1) + text.FlashColour(Color4.Red, 2000, Easing.OutQuint); + + text.FadeOut(200, Easing.InQuint); + } + }); + + UsesFixedAnchor = true; + } + + private IBindable direction = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + text.Alpha = Current.Value > 0 ? 1 : 0; + + direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy(); + direction.BindValueChanged(_ => updateAnchor()); + + // two schedules are required so that updateAnchor is executed in the next frame, + // which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer. + Schedule(() => Schedule(updateAnchor)); + } + + private void updateAnchor() + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + + // since we flip the vertical anchor when changing scroll direction, + // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. + if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) + Y = -Y; + } + + protected override IHasText CreateText() => text = new OsuSpriteText + { + Font = OsuFont.Torus.With(size: 32, fixedWidth: true), + }; + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 7f6540e7b514..b0a6086f2a96 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -2,8 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Scoring; @@ -12,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public class ManiaArgonSkinTransformer : SkinTransformer + public class ManiaArgonSkinTransformer : ArgonSkinTransformer { private readonly ManiaBeatmap beatmap; @@ -26,6 +29,34 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) { switch (lookup) { + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME); + + var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); + + rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = 200; + } + }) + { + new ArgonManiaComboCounter(), + }; + + return rulesetHUDComponents; + } + + break; + case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) From 95961dc98955aed19f1e5fd482ba119be868ec0b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:21:10 +0300 Subject: [PATCH 014/579] Add various visual test coverage --- .../Skinning/TestSceneComboCounter.cs | 38 +++++++++++++++++++ .../Skinning/TestScenePlayfield.cs | 13 +++++++ .../TestSceneManiaPlayerLegacySkin.cs | 36 ++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs new file mode 100644 index 000000000000..c1e1cfd7aff7 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Skinning.Argon; +using osu.Game.Rulesets.Mania.Skinning.Legacy; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public partial class TestSceneComboCounter : ManiaSkinnableTestScene + { + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset()); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("setup", () => SetContents(s => + { + if (s is ArgonSkin) + return new ArgonManiaComboCounter(); + + if (s is LegacySkin) + return new LegacyManiaComboCounter(); + + return new LegacyManiaComboCounter(); + })); + + AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20); + AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss })); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs index 29c47ca93ad1..110336d823c1 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs @@ -3,15 +3,22 @@ using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Mania.Tests.Skinning { public partial class TestScenePlayfield : ManiaSkinnableTestScene { + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset()); + private List stageDefinitions = new List(); [Test] @@ -29,6 +36,9 @@ public void TestSingleStage() Child = new ManiaPlayfield(stageDefinitions) }); }); + + AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20); + AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss })); } [TestCase(2)] @@ -54,6 +64,9 @@ public void TestDualStages(int columnCount) } }); }); + + AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20); + AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss })); } protected override IBeatmap CreateBeatmapForSkinProvider() diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs new file mode 100644 index 000000000000..0f10f96dbfdf --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs @@ -0,0 +1,36 @@ +// 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.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public partial class TestSceneManiaPlayerLegacySkin : LegacySkinPlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + // play with a converted beatmap to allow dual stages mod to work. + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(new RulesetInfo()); + + protected override bool HasCustomSteps => true; + + [Test] + public void TestSingleStage() + { + AddStep("Load single stage", LoadPlayer); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + } + + [Test] + public void TestDualStage() + { + AddStep("Load dual stage", () => LoadPlayer(new Mod[] { new ManiaModDualStages() })); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + } + } +} From 01219fa3712f46ea55c0df150c2c3be2c0a31a48 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:16:41 +0300 Subject: [PATCH 015/579] Disable "closest" anchor in mania combo counter for convenience --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +++ osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 3 ++- osu.Game/Skinning/ISerialisableDrawable.cs | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index f97218633376..cc1e5b26ec57 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -441,6 +441,9 @@ private bool placeComponent(ISerialisableDrawable component, bool applyDefaults drawableComponent.Origin = Anchor.TopCentre; drawableComponent.Anchor = Anchor.TopCentre; drawableComponent.Y = targetContainer.DrawSize.Y / 2; + + if (!component.SupportsClosestAnchor) + component.UsesFixedAnchor = true; } try diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index cf6fb60636f2..208bd710059f 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -233,7 +233,8 @@ protected override IEnumerable GetContextMenuItemsForSelection(IEnumer { var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors()) { - State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } + State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor), }, + Action = { Disabled = selection.Any(c => !c.Item.SupportsClosestAnchor) }, }; yield return new OsuMenuItem("Anchor") diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs index c9dcaca6d184..898186bcc12c 100644 --- a/osu.Game/Skinning/ISerialisableDrawable.cs +++ b/osu.Game/Skinning/ISerialisableDrawable.cs @@ -27,6 +27,14 @@ public interface ISerialisableDrawable : IDrawable /// bool IsEditable => true; + /// + /// Whether this component supports the "closest" anchor. + /// + /// + /// This is disabled by some components that shift position automatically. + /// + bool SupportsClosestAnchor => true; + /// /// In the context of the skin layout editor, whether this has a permanent anchor defined. /// If , this 's is automatically determined by proximity, From 02f5ea200ea7b464d6d78170dc7b7beeb3e61559 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 07:41:55 +0300 Subject: [PATCH 016/579] Fix failing tests --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 13 +++++------ .../Legacy/LegacyManiaComboCounter.cs | 22 ++++++++++--------- osu.Game/Screens/Play/Player.cs | 4 ++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 1c8e43345abf..ad515528fb08 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -7,9 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -24,15 +22,11 @@ public partial class ArgonManiaComboCounter : ComboCounter, ISerialisableDrawabl protected override double RollingDuration => 500; protected override Easing RollingEasing => Easing.OutQuint; - private DrawableManiaRuleset maniaRuleset = null!; - bool ISerialisableDrawable.SupportsClosestAnchor => false; [BackgroundDependencyLoader] - private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor) + private void load(ScoreProcessor scoreProcessor) { - maniaRuleset = (DrawableManiaRuleset)ruleset; - Current.BindTo(scoreProcessor.Combo); Current.BindValueChanged(combo => { @@ -50,6 +44,9 @@ private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor) UsesFixedAnchor = true; } + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + private IBindable direction = null!; protected override void LoadComplete() @@ -57,7 +54,7 @@ protected override void LoadComplete() base.LoadComplete(); text.Alpha = Current.Value > 0 ? 1 : 0; - direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy(); + direction = scrollingInfo.Direction.GetBoundCopy(); direction.BindValueChanged(_ => updateAnchor()); // two schedules are required so that updateAnchor is executed in the next frame, diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index fd309f625046..00619834c8c5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -3,9 +3,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -15,15 +14,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable { - private DrawableManiaRuleset maniaRuleset = null!; - bool ISerialisableDrawable.SupportsClosestAnchor => false; [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableRuleset ruleset) + private void load(ISkinSource skin) { - maniaRuleset = (DrawableManiaRuleset)ruleset; - DisplayedCountText.Anchor = Anchor.Centre; DisplayedCountText.Origin = Anchor.Centre; @@ -34,13 +29,16 @@ private void load(ISkinSource skin, DrawableRuleset ruleset) UsesFixedAnchor = true; } + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + private IBindable direction = null!; protected override void LoadComplete() { base.LoadComplete(); - direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy(); + direction = scrollingInfo.Direction.GetBoundCopy(); direction.BindValueChanged(_ => updateAnchor()); // two schedules are required so that updateAnchor is executed in the next frame, @@ -50,8 +48,12 @@ protected override void LoadComplete() private void updateAnchor() { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction + if (!Anchor.HasFlagFast(Anchor.y1)) + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + } // since we flip the vertical anchor when changing scroll direction, // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c960ac357f22..c10ef9731fe9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -32,6 +32,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play.HUD; @@ -224,6 +225,9 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, gameplayMods); dependencies.CacheAs(DrawableRuleset); + if (DrawableRuleset is IDrawableScrollingRuleset scrollingRuleset) + dependencies.CacheAs(scrollingRuleset.ScrollingInfo); + ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor.Mods.Value = gameplayMods; ScoreProcessor.ApplyBeatmap(playableBeatmap); From b5dbf24d2787569b4f813bf5631507492cdb0269 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 1 Feb 2024 10:19:09 -0500 Subject: [PATCH 017/579] early replay analysis settings version committing early version for others in the discussion to do their own testing with it --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 + .../UI/HitMarkerContainer.cs | 134 ++++++++++++++++++ .../UI/OsuAnalysisSettings.cs | 81 +++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 + .../PlayerSettingsOverlayStrings.cs | 15 ++ osu.Game/Rulesets/Ruleset.cs | 3 + osu.Game/Screens/Play/Player.cs | 2 +- .../Play/PlayerSettings/AnalysisSettings.cs | 18 +++ osu.Game/Screens/Play/ReplayPlayer.cs | 5 + 9 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs create mode 100644 osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs create mode 100644 osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6752712be1a5..358553ac591d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -38,6 +38,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; @@ -356,5 +357,7 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } + + public override AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset); } } diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs new file mode 100644 index 000000000000..a9fc42e596ef --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs @@ -0,0 +1,134 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI +{ + public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler + { + private Vector2 lastMousePosition; + + public Bindable HitMarkerEnabled = new BindableBool(); + public Bindable AimMarkersEnabled = new BindableBool(); + + public override bool ReceivePositionalInputAt(Vector2 _) => true; + + public bool OnPressed(KeyBindingPressEvent e) + { + if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) + { + AddMarker(e.Action); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) { } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + lastMousePosition = e.MousePosition; + + if (AimMarkersEnabled.Value) + { + AddMarker(null); + } + + return base.OnMouseMove(e); + } + + private void AddMarker(OsuAction? action) + { + Add(new HitMarkerDrawable(action) { Position = lastMousePosition }); + } + + private partial class HitMarkerDrawable : CompositeDrawable + { + private const double lifetime_duration = 1000; + private const double fade_out_time = 400; + + public override bool RemoveWhenNotAlive => true; + + public HitMarkerDrawable(OsuAction? action) + { + var colour = Colour4.Gray.Opacity(0.5F); + var length = 8; + var depth = float.MaxValue; + switch (action) + { + case OsuAction.LeftButton: + colour = Colour4.Orange; + length = 20; + depth = float.MinValue; + break; + case OsuAction.RightButton: + colour = Colour4.LightGreen; + length = 20; + depth = float.MinValue; + break; + } + + this.Depth = depth; + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 45, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 135, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 45, + Colour = colour + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 135, + Colour = colour + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + LifetimeStart = Time.Current; + LifetimeEnd = LifetimeStart + lifetime_duration; + + Scheduler.AddDelayed(() => + { + this.FadeOut(fade_out_time); + }, lifetime_duration - fade_out_time); + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs new file mode 100644 index 000000000000..4694c1a5606a --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Localisation; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.PlayerSettings; + +namespace osu.Game.Rulesets.Osu.UI +{ + public partial class OsuAnalysisSettings : AnalysisSettings + { + private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings"); + + protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset; + + private readonly PlayerCheckbox hitMarkerToggle; + private readonly PlayerCheckbox aimMarkerToggle; + private readonly PlayerCheckbox hideCursorToggle; + private readonly PlayerCheckbox? hiddenToggle; + + public OsuAnalysisSettings(DrawableRuleset drawableRuleset) + : base(drawableRuleset) + { + Children = new Drawable[] + { + hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, + aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, + hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } + }; + + // hidden stuff is just here for testing at the moment; to create the mod disabling functionality + + foreach (var mod in drawableRuleset.Mods) + { + if (mod is OsuModHidden) + { + logger.Add("Hidden is enabled", LogLevel.Debug); + Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" }); + break; + } + } + } + + protected override void LoadComplete() + { + drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); + drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + hideCursorToggle.Current.BindValueChanged(onCursorToggle); + hiddenToggle?.Current.BindValueChanged(onHiddenToggle); + } + + private void onCursorToggle(ValueChangedEvent hide) + { + // this only hides half the cursor + if (hide.NewValue) + { + drawableRuleset.Playfield.Cursor.Hide(); + } else + { + drawableRuleset.Playfield.Cursor.Show(); + } + } + + private void onHiddenToggle(ValueChangedEvent off) + { + if (off.NewValue) + { + logger.Add("Hidden off", LogLevel.Debug); + } else + { + logger.Add("Hidden on", LogLevel.Debug); + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 411a02c5aff3..d7e173217545 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -36,6 +36,9 @@ public partial class OsuPlayfield : Playfield private readonly JudgementPooler judgementPooler; public SmokeContainer Smoke { get; } + + public HitMarkerContainer MarkersContainer { get; } + public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -59,6 +62,7 @@ public OsuPlayfield() HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, + MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both } }; HitPolicy = new StartTimeOrderedHitPolicy(); diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs index 60874da561ff..f829fb4ac164 100644 --- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -19,6 +19,21 @@ public static class PlayerSettingsOverlayStrings /// public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame"); + /// + /// "Hit markers" + /// + public static LocalisableString HitMarkers => new TranslatableString(getKey(@"hit_markers"), @"Hit markers"); + + /// + /// "Aim markers" + /// + public static LocalisableString AimMarkers => new TranslatableString(getKey(@"aim_markers"), @"Aim markers"); + + /// + /// "Hide cursor" + /// + public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor"); + /// /// "Seek backward {0} seconds" /// diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 37a35fd3ae6c..5fbb23f094c6 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; using osu.Game.Users; @@ -402,5 +403,7 @@ protected Ruleset() /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. /// public virtual DifficultySection? CreateEditorDifficultySection() => null; + + public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ad1f9ec8979e..0fa714369354 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -115,7 +115,7 @@ public abstract partial class Player : ScreenWithBeatmapBackground, ISamplePlayb public GameplayState GameplayState { get; private set; } - private Ruleset ruleset; + protected Ruleset ruleset; public BreakOverlay BreakOverlay; diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs new file mode 100644 index 000000000000..122ca291428d --- /dev/null +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -0,0 +1,18 @@ +// 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.UI; + +namespace osu.Game.Screens.Play.PlayerSettings +{ + public partial class AnalysisSettings : PlayerSettingsGroup + { + protected DrawableRuleset drawableRuleset; + + public AnalysisSettings(DrawableRuleset drawableRuleset) + : base("Analysis Settings") + { + this.drawableRuleset = drawableRuleset; + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 3c5b85662a3a..0d877785e7fc 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -71,6 +71,11 @@ private void load(OsuConfigManager config) playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate); HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); + + var analysisSettings = ruleset.CreateAnalysisSettings(DrawableRuleset); + if (analysisSettings != null) { + HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); + } } protected override void PrepareReplay() From 288eed53df439c594ffab544d3b83d9b2ea9d343 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Sat, 10 Feb 2024 02:02:26 -0500 Subject: [PATCH 018/579] new features + improvements Hit & aim markers are skinnable. Hidden can be toggled off. Aim line with skinnable color was added. The fadeout time is based on the approach rate. Cursor hide fixed. --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 77 +++++++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 +- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 + .../Skinning/Default/DefaultHitMarker.cs | 71 +++++++ .../Skinning/Default/HitMarker.cs | 33 ++++ .../Legacy/OsuLegacySkinTransformer.cs | 19 ++ .../Skinning/OsuSkinColour.cs | 1 + .../UI/HitMarkerContainer.cs | 182 ++++++++++++------ .../UI/OsuAnalysisSettings.cs | 68 +++++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- .../PlayerSettingsOverlayStrings.cs | 5 + .../Rulesets/Mods/IToggleableVisibility.cs | 11 ++ 12 files changed, 390 insertions(+), 94 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs create mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 6dc0d5d5229f..a69bed6fb25a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -12,13 +14,14 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden, IHidesApproachCircles + public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility { [SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")] public Bindable OnlyFadeApproachCircles { get; } = new BindableBool(); @@ -28,24 +31,41 @@ public class OsuModHidden : ModHidden, IHidesApproachCircles public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) }; + private bool toggledOff = false; + private IBeatmap? appliedBeatmap; + public const double FADE_IN_DURATION_MULTIPLIER = 0.4; public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick); - public override void ApplyToBeatmap(IBeatmap beatmap) + public override void ApplyToBeatmap(IBeatmap? beatmap = null) { + if (beatmap is not null) + appliedBeatmap = beatmap; + else if (appliedBeatmap is null) + return; + else + beatmap = appliedBeatmap; + base.ApplyToBeatmap(beatmap); foreach (var obj in beatmap.HitObjects.OfType()) applyFadeInAdjustment(obj); + } - static void applyFadeInAdjustment(OsuHitObject osuObject) - { - osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; - foreach (var nested in osuObject.NestedHitObjects.OfType()) - applyFadeInAdjustment(nested); - } + private static void applyFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; + foreach (var nested in osuObject.NestedHitObjects.OfType()) + applyFadeInAdjustment(nested); + } + + private static void revertFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt); + foreach (var nested in osuObject.NestedHitObjects.OfType()) + revertFadeInAdjustment(nested); } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -60,7 +80,7 @@ protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility) { - if (!(drawableObject is DrawableOsuHitObject drawableOsuObject)) + if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff) return; OsuHitObject hitObject = drawableOsuObject.HitObject; @@ -183,8 +203,11 @@ private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVis } } - private static void hideSpinnerApproachCircle(DrawableSpinner spinner) + private void hideSpinnerApproachCircle(DrawableSpinner spinner) { + if (toggledOff) + return; + var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle; if (approachCircle == null) return; @@ -192,5 +215,39 @@ private static void hideSpinnerApproachCircle(DrawableSpinner spinner) using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt)) approachCircle.Hide(); } + + public void ToggleOffVisibility(Playfield playfield) + { + if (toggledOff) + return; + + toggledOff = true; + + if (appliedBeatmap is not null) + foreach (var obj in appliedBeatmap.HitObjects.OfType()) + revertFadeInAdjustment(obj); + + foreach (var dho in playfield.AllHitObjects) + { + dho.RefreshStateTransforms(); + } + } + + public void ToggleOnVisibility(Playfield playfield) + { + if (!toggledOff) + return; + + toggledOff = false; + + if (appliedBeatmap is not null) + ApplyToBeatmap(); + + foreach (var dho in playfield.AllHitObjects) + { + dho.RefreshStateTransforms(); + ApplyToDrawableHitObject(dho); + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 74631400cae9..60305ed95378 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -154,17 +154,19 @@ protected OsuHitObject() }); } + // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. + static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN); + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN); - // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. - // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. - // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. - // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. - TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN); + TimeFadeIn = CalculateTimeFadeIn(TimePreempt); Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true); } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 52fdfea95f96..75db18d345e9 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -22,5 +22,8 @@ public enum OsuSkinComponents SpinnerBody, CursorSmoke, ApproachCircle, + HitMarkerLeft, + HitMarkerRight, + AimMarker } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs new file mode 100644 index 000000000000..f4c11e6c8a08 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs @@ -0,0 +1,71 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public partial class DefaultHitMarker : CompositeDrawable + { + public DefaultHitMarker(OsuAction? action) + { + var (colour, length, hasBorder) = getConfig(action); + + if (hasBorder) + { + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 45, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 135, + Colour = Colour4.Black.Opacity(0.5F) + } + }; + } + + AddRangeInternal(new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 45, + Colour = colour + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 135, + Colour = colour + } + }); + } + + private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) + { + switch (action) + { + case OsuAction.LeftButton: + return (Colour4.Orange, 20, true); + case OsuAction.RightButton: + return (Colour4.LightGreen, 20, true); + default: + return (Colour4.Gray.Opacity(0.3F), 8, false); + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs new file mode 100644 index 000000000000..d86e3d4f79a4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs @@ -0,0 +1,33 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public partial class HitMarker : Sprite + { + private readonly OsuAction? action; + + public HitMarker(OsuAction? action = null) + { + this.action = action; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + switch (action) + { + case OsuAction.LeftButton: + Texture = skin.GetTexture(@"hitmarker-left"); + break; + case OsuAction.RightButton: + Texture = skin.GetTexture(@"hitmarker-right"); + break; + default: + Texture = skin.GetTexture(@"aimmarker"); + break; + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d2ebc68c5225..84c055fe5e50 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -168,6 +169,24 @@ public OsuLegacySkinTransformer(ISkin skin) return null; + case OsuSkinComponents.HitMarkerLeft: + if (GetTexture(@"hitmarker-left") != null) + return new HitMarker(OsuAction.LeftButton); + + return null; + + case OsuSkinComponents.HitMarkerRight: + if (GetTexture(@"hitmarker-right") != null) + return new HitMarker(OsuAction.RightButton); + + return null; + + case OsuSkinComponents.AimMarker: + if (GetTexture(@"aimmarker") != null) + return new HitMarker(); + + return null; + default: throw new UnsupportedSkinComponentException(lookup); } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index 24f9217a5f45..5c864fb6c21a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -10,5 +10,6 @@ public enum OsuSkinColour SliderBall, SpinnerBackground, StarBreakAdditive, + ReplayAimLine, } } diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs index a9fc42e596ef..01877a91851a 100644 --- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs @@ -10,124 +10,186 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI { public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler { private Vector2 lastMousePosition; + private Vector2? lastlastMousePosition; + private double? timePreempt; + private double TimePreempt + { + get => timePreempt ?? default_time_preempt; + set => timePreempt = value; + } public Bindable HitMarkerEnabled = new BindableBool(); public Bindable AimMarkersEnabled = new BindableBool(); + public Bindable AimLinesEnabled = new BindableBool(); + + private const double default_time_preempt = 1000; + + private readonly HitObjectContainer hitObjectContainer; public override bool ReceivePositionalInputAt(Vector2 _) => true; + public HitMarkerContainer(HitObjectContainer hitObjectContainer) + { + this.hitObjectContainer = hitObjectContainer; + } + public bool OnPressed(KeyBindingPressEvent e) { if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) { + updateTimePreempt(); AddMarker(e.Action); } return false; } - public void OnReleased(KeyBindingReleaseEvent e) { } + public void OnReleased(KeyBindingReleaseEvent e) + { + } protected override bool OnMouseMove(MouseMoveEvent e) { + lastlastMousePosition = lastMousePosition; lastMousePosition = e.MousePosition; if (AimMarkersEnabled.Value) { + updateTimePreempt(); AddMarker(null); } + if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition) + { + if (!AimMarkersEnabled.Value) + updateTimePreempt(); + Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt)); + } + return base.OnMouseMove(e); } private void AddMarker(OsuAction? action) { - Add(new HitMarkerDrawable(action) { Position = lastMousePosition }); + var component = OsuSkinComponents.AimMarker; + switch(action) + { + case OsuAction.LeftButton: + component = OsuSkinComponents.HitMarkerLeft; + break; + case OsuAction.RightButton: + component = OsuSkinComponents.HitMarkerRight; + break; + } + + Add(new HitMarkerDrawable(action, component, TimePreempt) + { + Position = lastMousePosition, + Origin = Anchor.Centre, + Depth = action == null ? float.MaxValue : float.MinValue + }); + } + + private void updateTimePreempt() + { + var hitObject = getHitObject(); + if (hitObject == null) + return; + + TimePreempt = hitObject.TimePreempt; + } + + private OsuHitObject? getHitObject() + { + foreach (var dho in hitObjectContainer.Objects) + return (dho as DrawableOsuHitObject)?.HitObject; + return null; } - private partial class HitMarkerDrawable : CompositeDrawable + private partial class HitMarkerDrawable : SkinnableDrawable { - private const double lifetime_duration = 1000; - private const double fade_out_time = 400; + private readonly double lifetimeDuration; + private readonly double fadeOutTime; public override bool RemoveWhenNotAlive => true; - public HitMarkerDrawable(OsuAction? action) + public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt) + : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action)) + { + fadeOutTime = timePreempt / 2; + lifetimeDuration = timePreempt + fadeOutTime; + } + + protected override void LoadComplete() { - var colour = Colour4.Gray.Opacity(0.5F); - var length = 8; - var depth = float.MaxValue; - switch (action) + base.LoadComplete(); + + LifetimeStart = Time.Current; + LifetimeEnd = LifetimeStart + lifetimeDuration; + + Scheduler.AddDelayed(() => { - case OsuAction.LeftButton: - colour = Colour4.Orange; - length = 20; - depth = float.MinValue; - break; - case OsuAction.RightButton: - colour = Colour4.LightGreen; - length = 20; - depth = float.MinValue; - break; - } - - this.Depth = depth; - - InternalChildren = new Drawable[] + this.FadeOut(fadeOutTime); + }, lifetimeDuration - fadeOutTime); + } + } + + private partial class AimLineDrawable : CompositeDrawable + { + private readonly double lifetimeDuration; + private readonly double fadeOutTime; + + public override bool RemoveWhenNotAlive => true; + + public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt) + { + fadeOutTime = timePreempt / 2; + lifetimeDuration = timePreempt + fadeOutTime; + + float distance = Vector2.Distance(fromP, toP); + Vector2 direction = (toP - fromP); + InternalChild = new Box { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 45, - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 135, - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 45, - Colour = colour - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 135, - Colour = colour - } + Position = fromP + (direction / 2), + Size = new Vector2(distance, 1), + Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)), + Origin = Anchor.Centre }; } + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; + color.A = 127; + InternalChild.Colour = color; + } + protected override void LoadComplete() { base.LoadComplete(); LifetimeStart = Time.Current; - LifetimeEnd = LifetimeStart + lifetime_duration; + LifetimeEnd = LifetimeStart + lifetimeDuration; Scheduler.AddDelayed(() => { - this.FadeOut(fade_out_time); - }, lifetime_duration - fade_out_time); + this.FadeOut(fadeOutTime); + }, lifetimeDuration - fadeOutTime); } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 4694c1a5606a..050675d97086 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -4,7 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Logging; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -15,14 +17,13 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { - private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings"); - protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset; private readonly PlayerCheckbox hitMarkerToggle; private readonly PlayerCheckbox aimMarkerToggle; private readonly PlayerCheckbox hideCursorToggle; - private readonly PlayerCheckbox? hiddenToggle; + private readonly PlayerCheckbox aimLinesToggle; + private readonly FillFlowContainer modTogglesContainer; public OsuAnalysisSettings(DrawableRuleset drawableRuleset) : base(drawableRuleset) @@ -31,18 +32,36 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) { hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, - hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } + aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, + hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }, + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.X, + Height = ModSwitchSmall.DEFAULT_SIZE, + ScrollbarOverlapsContent = false, + Child = modTogglesContainer = new FillFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + } + } }; - - // hidden stuff is just here for testing at the moment; to create the mod disabling functionality foreach (var mod in drawableRuleset.Mods) { - if (mod is OsuModHidden) + if (mod is IToggleableVisibility toggleableMod) { - logger.Add("Hidden is enabled", LogLevel.Debug); - Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" }); - break; + var modSwitch = new SelectableModSwitchSmall(mod) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Active = { Value = true } + }; + modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v)); + modTogglesContainer.Add(modSwitch); } } } @@ -51,8 +70,8 @@ protected override void LoadComplete() { drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); hideCursorToggle.Current.BindValueChanged(onCursorToggle); - hiddenToggle?.Current.BindValueChanged(onHiddenToggle); } private void onCursorToggle(ValueChangedEvent hide) @@ -60,21 +79,34 @@ private void onCursorToggle(ValueChangedEvent hide) // this only hides half the cursor if (hide.NewValue) { - drawableRuleset.Playfield.Cursor.Hide(); + drawableRuleset.Playfield.Cursor.FadeOut(); } else { - drawableRuleset.Playfield.Cursor.Show(); + drawableRuleset.Playfield.Cursor.FadeIn(); } } - private void onHiddenToggle(ValueChangedEvent off) + private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled) { - if (off.NewValue) + if (toggled.NewValue) { - logger.Add("Hidden off", LogLevel.Debug); + mod.ToggleOnVisibility(drawableRuleset.Playfield); } else { - logger.Add("Hidden on", LogLevel.Debug); + mod.ToggleOffVisibility(drawableRuleset.Playfield); + } + } + + private partial class SelectableModSwitchSmall : ModSwitchSmall + { + public SelectableModSwitchSmall(IMod mod) + : base(mod) + {} + + protected override bool OnClick(ClickEvent e) + { + Active.Value = !Active.Value; + return true; } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index d7e173217545..3cb3c50ef722 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -62,7 +62,7 @@ public OsuPlayfield() HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both } + MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both } }; HitPolicy = new StartTimeOrderedHitPolicy(); diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs index f829fb4ac164..017cc9bf82c4 100644 --- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -34,6 +34,11 @@ public static class PlayerSettingsOverlayStrings /// public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor"); + /// + /// "Aim lines" + /// + public static LocalisableString AimLines => new TranslatableString(getKey(@"aim_lines"), @"Aim lines"); + /// /// "Seek backward {0} seconds" /// diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs new file mode 100644 index 000000000000..5d90c24b9e35 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs @@ -0,0 +1,11 @@ +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mods +{ + public interface IToggleableVisibility + { + public void ToggleOffVisibility(Playfield playfield); + + public void ToggleOnVisibility(Playfield playfield); + } +} \ No newline at end of file From 1d552e7c90b8d3f7350fecaa43f5759b0154eaa7 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Tue, 20 Feb 2024 22:28:25 -0500 Subject: [PATCH 019/579] move skin component logic --- .../Default/OsuTrianglesSkinTransformer.cs | 22 +++++++++++++++++++ .../Legacy/OsuLegacySkinTransformer.cs | 22 ------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs index 7a4c768aa25f..69ef1d9dc095 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs @@ -29,6 +29,28 @@ public OsuTrianglesSkinTransformer(ISkin skin) return new DefaultJudgementPieceSliderTickMiss(result); } + break; + case OsuSkinComponentLookup osuComponent: + switch (osuComponent.Component) + { + case OsuSkinComponents.HitMarkerLeft: + if (GetTexture(@"hitmarker-left") != null) + return new HitMarker(OsuAction.LeftButton); + + return null; + + case OsuSkinComponents.HitMarkerRight: + if (GetTexture(@"hitmarker-right") != null) + return new HitMarker(OsuAction.RightButton); + + return null; + + case OsuSkinComponents.AimMarker: + if (GetTexture(@"aimmarker") != null) + return new HitMarker(); + + return null; + } break; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 84c055fe5e50..a931d3ecbf32 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -168,27 +167,6 @@ public OsuLegacySkinTransformer(ISkin skin) return new LegacyApproachCircle(); return null; - - case OsuSkinComponents.HitMarkerLeft: - if (GetTexture(@"hitmarker-left") != null) - return new HitMarker(OsuAction.LeftButton); - - return null; - - case OsuSkinComponents.HitMarkerRight: - if (GetTexture(@"hitmarker-right") != null) - return new HitMarker(OsuAction.RightButton); - - return null; - - case OsuSkinComponents.AimMarker: - if (GetTexture(@"aimmarker") != null) - return new HitMarker(); - - return null; - - default: - throw new UnsupportedSkinComponentException(lookup); } } From 35b89966bccc2455034c7aed973cf2fefbf87ca7 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Wed, 21 Feb 2024 23:23:40 -0500 Subject: [PATCH 020/579] revert mod visibility toggle --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 79 +++---------------- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 ++- .../UI/OsuAnalysisSettings.cs | 57 +------------ .../Rulesets/Mods/IToggleableVisibility.cs | 11 --- 4 files changed, 17 insertions(+), 142 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index a69bed6fb25a..e45daed9199c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Graphics.Transforms; using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -14,14 +12,13 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility + public class OsuModHidden : ModHidden, IHidesApproachCircles { [SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")] public Bindable OnlyFadeApproachCircles { get; } = new BindableBool(); @@ -31,41 +28,24 @@ public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibil public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) }; - private bool toggledOff = false; - private IBeatmap? appliedBeatmap; - public const double FADE_IN_DURATION_MULTIPLIER = 0.4; public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick); - public override void ApplyToBeatmap(IBeatmap? beatmap = null) + public override void ApplyToBeatmap(IBeatmap beatmap) { - if (beatmap is not null) - appliedBeatmap = beatmap; - else if (appliedBeatmap is null) - return; - else - beatmap = appliedBeatmap; - base.ApplyToBeatmap(beatmap); foreach (var obj in beatmap.HitObjects.OfType()) applyFadeInAdjustment(obj); - } - private static void applyFadeInAdjustment(OsuHitObject osuObject) - { - osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; - foreach (var nested in osuObject.NestedHitObjects.OfType()) - applyFadeInAdjustment(nested); - } - - private static void revertFadeInAdjustment(OsuHitObject osuObject) - { - osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt); - foreach (var nested in osuObject.NestedHitObjects.OfType()) - revertFadeInAdjustment(nested); + static void applyFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; + foreach (var nested in osuObject.NestedHitObjects.OfType()) + applyFadeInAdjustment(nested); + } } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -80,7 +60,7 @@ protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility) { - if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff) + if (!(drawableObject is DrawableOsuHitObject drawableOsuObject)) return; OsuHitObject hitObject = drawableOsuObject.HitObject; @@ -203,11 +183,8 @@ private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVis } } - private void hideSpinnerApproachCircle(DrawableSpinner spinner) + private static void hideSpinnerApproachCircle(DrawableSpinner spinner) { - if (toggledOff) - return; - var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle; if (approachCircle == null) return; @@ -215,39 +192,5 @@ private void hideSpinnerApproachCircle(DrawableSpinner spinner) using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt)) approachCircle.Hide(); } - - public void ToggleOffVisibility(Playfield playfield) - { - if (toggledOff) - return; - - toggledOff = true; - - if (appliedBeatmap is not null) - foreach (var obj in appliedBeatmap.HitObjects.OfType()) - revertFadeInAdjustment(obj); - - foreach (var dho in playfield.AllHitObjects) - { - dho.RefreshStateTransforms(); - } - } - - public void ToggleOnVisibility(Playfield playfield) - { - if (!toggledOff) - return; - - toggledOff = false; - - if (appliedBeatmap is not null) - ApplyToBeatmap(); - - foreach (var dho in playfield.AllHitObjects) - { - dho.RefreshStateTransforms(); - ApplyToDrawableHitObject(dho); - } - } } -} +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 60305ed95378..74631400cae9 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -154,19 +154,17 @@ protected OsuHitObject() }); } - // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. - // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. - // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. - // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. - static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN); - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN); - TimeFadeIn = CalculateTimeFadeIn(TimePreempt); + // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. + TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN); Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 050675d97086..319ae3e1f5a4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -8,7 +8,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Localisation; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.PlayerSettings; @@ -23,7 +22,6 @@ public partial class OsuAnalysisSettings : AnalysisSettings private readonly PlayerCheckbox aimMarkerToggle; private readonly PlayerCheckbox hideCursorToggle; private readonly PlayerCheckbox aimLinesToggle; - private readonly FillFlowContainer modTogglesContainer; public OsuAnalysisSettings(DrawableRuleset drawableRuleset) : base(drawableRuleset) @@ -33,37 +31,8 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, - hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }, - new OsuScrollContainer(Direction.Horizontal) - { - RelativeSizeAxes = Axes.X, - Height = ModSwitchSmall.DEFAULT_SIZE, - ScrollbarOverlapsContent = false, - Child = modTogglesContainer = new FillFlowContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X - } - } + hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } }; - - foreach (var mod in drawableRuleset.Mods) - { - if (mod is IToggleableVisibility toggleableMod) - { - var modSwitch = new SelectableModSwitchSmall(mod) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Active = { Value = true } - }; - modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v)); - modTogglesContainer.Add(modSwitch); - } - } } protected override void LoadComplete() @@ -85,29 +54,5 @@ private void onCursorToggle(ValueChangedEvent hide) drawableRuleset.Playfield.Cursor.FadeIn(); } } - - private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled) - { - if (toggled.NewValue) - { - mod.ToggleOnVisibility(drawableRuleset.Playfield); - } else - { - mod.ToggleOffVisibility(drawableRuleset.Playfield); - } - } - - private partial class SelectableModSwitchSmall : ModSwitchSmall - { - public SelectableModSwitchSmall(IMod mod) - : base(mod) - {} - - protected override bool OnClick(ClickEvent e) - { - Active.Value = !Active.Value; - return true; - } - } } } \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs deleted file mode 100644 index 5d90c24b9e35..000000000000 --- a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs +++ /dev/null @@ -1,11 +0,0 @@ -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mods -{ - public interface IToggleableVisibility - { - public void ToggleOffVisibility(Playfield playfield); - - public void ToggleOnVisibility(Playfield playfield); - } -} \ No newline at end of file From f9d9df30b2104322920eb8326ad01ea946f87f06 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 22 Feb 2024 07:31:15 -0500 Subject: [PATCH 021/579] add test for HitMarkerContainer --- .../Resources/special-skin/aimmarker@2x.png | Bin 0 -> 648 bytes .../special-skin/hitmarker-left@2x.png | Bin 0 -> 2127 bytes .../special-skin/hitmarker-right@2x.png | Bin 0 -> 1742 bytes .../Resources/special-skin/skin.ini | 5 +- .../TestSceneHitMarker.cs | 134 ++++++++++++++++++ 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-left@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0b2a554193f08f7984568980956a3db8a6b39800 GIT binary patch literal 648 zcmV;30(bq1P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a6PO$WR@`E-K4rtTK|H%@ z>74h8L#!+*#OK7523?T&k?XR{Z=6dG3p_JqWYhD+A!4!A#c~(3vY`^s5JwbMqkJLf zvch?bvs$gQ_C5Ivg9U9R!*!aYNMH#`q#!~@9TikzAxf)8iitGs$36Tbjz2{%nOqex zax9<*6_Voz|AXJ%n#JiUHz^ngdS7h&V+;uF0jdyW16NwdUuyz$pQJZB zTI2{A+y*YLJDR))TI00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=mHiD95ZRmtwR6+02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{003Y~L_t&-(~Xd^4Zt7_1kYC1UL}8=Py}}=u;}y?!uSxX)0000EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000IiNkls_o@u5n{w3H!5jp#I_s}mSL6nvbfkWc#_vuLYo^#J%=bYz0&vPDu>`?~P z0Nc!V3E&1C=JO;l4U7YWzyQ!=wjUbdA^U#|Xawp!3a-6gDOj^!(!4I&S*VDQsaC;d zkpu@oS~HUIlo5?2^x2VUX1*t+Nq-yB9v@8*1=@kX09PKhkXqoU8}iuo%6H`9I*-*= zs7QtWsq|QHIFq^%*3_$0#@g!%TuB`Tz#)<-Rfv`s2s4$%QoO2IwpJ8aHbxR!qW7trJguJ@1?U45bE-`_uOK{-b-%3G@PfU<~*e zh?{*?U>&d#r~s;f+WnivkpmT$8`bNrWoNG~1b*F}+HfgG2XGX)$`bIrTV}Ye*3uC> zq=ruJv2<)!2?D2qeSqx&#}JNi2t zZQa#wVF)+@__FvNA8?}DEev%w+PVYHj{b&HVE||~=kU;$=+xmIQrE;mC2+(ioi{B_ z14~B(&~wBmou4?U1PDHZc=jd~eOHCfJ4>$%CvGf!H$C^B1-{^CW zORYQQPIC*FJ;-)C)w)yeyz;EQuf9aM2(<9%X{j#}E?#-o-e-zAb+2tE-D7|^5ATq? zKPt;x^IFGEMO1bn`Y0b*rGQ)vpN56TnkFjz%cEl&04>UXaP19WdWiP+eR%_|# zY_xTo)~RR(2`K`4IvZ`>ZMBvT;GKKA9b5Amiycx!=6|Arl}AIhTNsKRQbSww88cm_ z&$%e?{q<>UQ8Hr~O=r?UpqZ7)iIaOQk2_>R_~GAElfeGZc(EJun2f*VoHpGKA1fE% zW|d(4CFk^pJSI&K{I>Z$^s8!FUC@l#qnEW&;P)$7NN6_2le@kqBsYFlnE6LgSAh=E zd{|fKvAT}?({|u}RzB|^_owZ39;*weyX}g26oT_FIwQc`1A4KK8XGV-|DrSEQ3wKM zB2cr}D+T>i=`k~&xS0b&ZUX20Q|Yn2UMUFFh`_d*^^(>b&ZNwsC|Bt14QEm{>m?1? zCIV$%m+ZU{)>JdH%N6_=!kX%J$xfh521*JQQMx*17-o2yD~w&8GS(IB2Y0l0XL0O! zQb(~!i_VG2DnSO4Y0bbPVkC7`;L~hoUhdplS)RM<5J{vpdqa)piM1;R`uq0a*2A}}}-&CFL8OK}!6 zfVo0NWw8_=iDu@3K@k|}329PQv20~AjhQP{RTazDo{%Q7nAu-FPUGNcf@mb6MfPtJ zM}Ybq5K_N?lQfP9prGz^zLl@R8mKqoXdRswQBoUra#pR{83{q(7nteTADoK?pG`A`9D8* zL+g+7N8rOR69RB?D8@HbQD5`&4x1ww%{(zYhq8?E{44b(!oD|l*)8v0UWm1Qq+bAj zbN5U4x*zH93LD?uTB69FHx-%Cyv%2>X8fJ)3^^T6(_aU)m&#*Br_F z6_Nx3XU28k6Kk-%u-)ePB(8b=QDY16?Y- z$_egQ2*3<*<;=LgJzv(Xzwp`DthMUSb0Oo$z$d`aQkhub=n{^MKyPm^JPw7#IhEi{TDHR{#GTxr2YT^002ovPDHLk FV1jr`?veli literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..66be9a80ee2e2ae4c6dc6915d2b7ca5cf8ab589f GIT binary patch literal 1742 zcmV;<1~K`GP)EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000D~Nkl1L9Yb|fH_ftE7RjjO?hFdLVhHsD6;Mi3NQZON`8V+<3BQA3EPF^Lyy zBFH5ssJYD~PVPNjt#956lhOc~_xg%spW#euk$w}6}R9gK=vhuEEC9o1$c~`Rf z(Ry2wlf>QYeKL21@Rg4>x`?ys*smS!IAyn7e8E7rV7}_2!Kl|YJi?#x1%84Syp!In zkY2Z>ZC|ASeH8V&R&~)}wqQQKV8CuUoJ!|IpZh27F_`+c=GVn>f!toVMTXssZ0rPq-2F z7)4*YhF6Y6=fW|AnK={TxGQO->3NOv?ZHqu?n-9PL^x&;=hC%on8f+eXCO0UEIMoi zC$QrUyo)<T*3ZV=R?f-~Sw)q8_7}ITNXzj(Ynz8XY#0nKO|Ffd3lJ+SQw? zo^T_5u}|1KI1!G!Qa#~D8k*bxlSMfdpVSn!s$M%F!q0GjixAm?KUED#k}2O6e=Ub! zzT9@~mdieLFWo14Y(4bY?{@Z~d#M9B__8N*HojnoWl8a{S^UD*#Oe5q57df^KXo6y zFS^)9_p=4_sqNTj>tdf)v)O`qjqaxoT%0!kCVZ*Rs)f?;&EU6Nn8-Z~eiR+BtjUAq zuj+95Y2#Stj`7sR9`G*v)Lv2)QZ=X0g)O!$}Y)kHjB1^&ZygL zaa;~xW2Fp;tl;b7xLnk27M%fmLZ@PB*b@vZ9}Jg6605gAXe zstYZ)p{)uZh6ZtROM+Y(5y>UDycZfo_&zMvtXfj*ar#}oX-JZ!IZ2XB>92W{iM*rM zPniAbKjTyQRE^sNDlJm64K~qIM5Tc?-B3Fj<2yzt(TD^gFHQ4B!vebI{%dk!P-X kiyU)`Hc@tO_2Ah*0YN2jCf7K0Z2$lO07*qoM6N<$f~iM7hyVZp literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini index 9d16267d733b..2952948f452a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini @@ -1,4 +1,7 @@ [General] Version: latest HitCircleOverlayAboveNumber: 0 -HitCirclePrefix: display \ No newline at end of file +HitCirclePrefix: display + +[Colours] +ReplayAimLine: 0,0,255 \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs new file mode 100644 index 000000000000..a0748e31af8d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs @@ -0,0 +1,134 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Framework.Logging; +using osu.Framework.Testing.Input; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneHitMarker : OsuSkinnableTestScene + { + [Test] + public void TestHitMarkers() + { + var markerContainers = new List(); + + AddStep("Create hit markers", () => + { + markerContainers.Clear(); + SetContents(_ => { + markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer()) + { + HitMarkerEnabled = { Value = true }, + AimMarkersEnabled = { Value = true }, + AimLinesEnabled = { Value = true }, + RelativeSizeAxes = Axes.Both + }); + + return new HitMarkerInputManager + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.95f), + Child = markerContainers[^1], + }; + }); + }); + + AddUntilStep("Until skinnable expires", () => + { + if (markerContainers.Count == 0) + return false; + + Logger.Log("How many: " + markerContainers.Count); + + foreach (var markerContainer in markerContainers) + { + if (markerContainer.Children.Count != 0) + return false; + } + + return true; + }); + } + + private partial class HitMarkerInputManager : ManualInputManager + { + private double? startTime; + + public HitMarkerInputManager() + { + UseParentInput = false; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + MoveMouseTo(ToScreenSpace(DrawSize / 2)); + } + + protected override void Update() + { + base.Update(); + + const float spin_angle = 4 * MathF.PI; + + startTime ??= Time.Current; + + float fraction = (float)((Time.Current - startTime) / 5_000); + + float angle = fraction * spin_angle; + float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2; + + Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2; + MoveMouseTo(ToScreenSpace(pos)); + } + } + + private partial class TestHitMarkerContainer : HitMarkerContainer + { + private double? lastClick; + private double? startTime; + private bool finishedDrawing = false; + private bool leftOrRight = false; + + public TestHitMarkerContainer(HitObjectContainer hitObjectContainer) + : base(hitObjectContainer) + { + } + + protected override void Update() + { + base.Update(); + + if (finishedDrawing) + return; + + startTime ??= lastClick ??= Time.Current; + + if (startTime + 5_000 <= Time.Current) + { + finishedDrawing = true; + HitMarkerEnabled.Value = AimMarkersEnabled.Value = AimLinesEnabled.Value = false; + return; + } + + if (lastClick + 400 <= Time.Current) + { + OnPressed(new KeyBindingPressEvent(new InputState(), leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton)); + leftOrRight = !leftOrRight; + lastClick = Time.Current; + } + } + } + } +} \ No newline at end of file From af13389a9e8fc58ef549cb8e9920375c22dc99ef Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 22 Feb 2024 07:31:56 -0500 Subject: [PATCH 022/579] fix hit marker skinnables --- .../Default/OsuTrianglesSkinTransformer.cs | 22 ------------------- .../Legacy/OsuLegacySkinTransformer.cs | 19 ++++++++++++++++ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs index 69ef1d9dc095..7a4c768aa25f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs @@ -29,28 +29,6 @@ public OsuTrianglesSkinTransformer(ISkin skin) return new DefaultJudgementPieceSliderTickMiss(result); } - break; - case OsuSkinComponentLookup osuComponent: - switch (osuComponent.Component) - { - case OsuSkinComponents.HitMarkerLeft: - if (GetTexture(@"hitmarker-left") != null) - return new HitMarker(OsuAction.LeftButton); - - return null; - - case OsuSkinComponents.HitMarkerRight: - if (GetTexture(@"hitmarker-right") != null) - return new HitMarker(OsuAction.RightButton); - - return null; - - case OsuSkinComponents.AimMarker: - if (GetTexture(@"aimmarker") != null) - return new HitMarker(); - - return null; - } break; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index a931d3ecbf32..097f1732e2a0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.Skinning.Default; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -167,6 +168,24 @@ public OsuLegacySkinTransformer(ISkin skin) return new LegacyApproachCircle(); return null; + + case OsuSkinComponents.HitMarkerLeft: + if (GetTexture(@"hitmarker-left") != null) + return new HitMarker(OsuAction.LeftButton); + + return null; + + case OsuSkinComponents.HitMarkerRight: + if (GetTexture(@"hitmarker-right") != null) + return new HitMarker(OsuAction.RightButton); + + return null; + + case OsuSkinComponents.AimMarker: + if (GetTexture(@"aimmarker") != null) + return new HitMarker(); + + return null; } } From 45444b39d461ca9c497cc7ffdac8402b98e26e03 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 22 Feb 2024 19:01:52 -0500 Subject: [PATCH 023/579] fix formatting issues --- .../TestSceneHitMarker.cs | 11 ++++--- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Skinning/Default/DefaultHitMarker.cs | 7 ++++- .../Skinning/Default/HitMarker.cs | 7 ++++- .../Legacy/OsuLegacySkinTransformer.cs | 4 +-- .../UI/HitMarkerContainer.cs | 31 +++++++++---------- .../UI/OsuAnalysisSettings.cs | 22 ++++++------- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- .../Play/PlayerSettings/AnalysisSettings.cs | 6 ++-- osu.Game/Screens/Play/ReplayPlayer.cs | 5 ++- 12 files changed, 53 insertions(+), 48 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs index a0748e31af8d..7ba681b50f1d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs @@ -25,8 +25,9 @@ public void TestHitMarkers() AddStep("Create hit markers", () => { markerContainers.Clear(); - SetContents(_ => { - markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer()) + SetContents(_ => + { + markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer()) { HitMarkerEnabled = { Value = true }, AimMarkersEnabled = { Value = true }, @@ -98,8 +99,8 @@ private partial class TestHitMarkerContainer : HitMarkerContainer { private double? lastClick; private double? startTime; - private bool finishedDrawing = false; - private bool leftOrRight = false; + private bool finishedDrawing; + private bool leftOrRight; public TestHitMarkerContainer(HitObjectContainer hitObjectContainer) : base(hitObjectContainer) @@ -131,4 +132,4 @@ protected override void Update() } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index e45daed9199c..6dc0d5d5229f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -193,4 +193,4 @@ private static void hideSpinnerApproachCircle(DrawableSpinner spinner) approachCircle.Hide(); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 358553ac591d..224d08cbd505 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -358,6 +358,6 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } - public override AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset); + public override AnalysisSettings CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs index f4c11e6c8a08..7dabb5182f70 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -61,11 +64,13 @@ public DefaultHitMarker(OsuAction? action) { case OsuAction.LeftButton: return (Colour4.Orange, 20, true); + case OsuAction.RightButton: return (Colour4.LightGreen, 20, true); + default: return (Colour4.Gray.Opacity(0.3F), 8, false); } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs index d86e3d4f79a4..28877345d0b0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Game.Skinning; @@ -21,13 +24,15 @@ private void load(ISkinSource skin) case OsuAction.LeftButton: Texture = skin.GetTexture(@"hitmarker-left"); break; + case OsuAction.RightButton: Texture = skin.GetTexture(@"hitmarker-right"); break; + default: Texture = skin.GetTexture(@"aimmarker"); break; } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 097f1732e2a0..61f9eebd862f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -168,7 +168,7 @@ public OsuLegacySkinTransformer(ISkin skin) return new LegacyApproachCircle(); return null; - + case OsuSkinComponents.HitMarkerLeft: if (GetTexture(@"hitmarker-left") != null) return new HitMarker(OsuAction.LeftButton); @@ -184,7 +184,7 @@ public OsuLegacySkinTransformer(ISkin skin) case OsuSkinComponents.AimMarker: if (GetTexture(@"aimmarker") != null) return new HitMarker(); - + return null; } } diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs index 01877a91851a..e8916ea5451a 100644 --- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs @@ -25,12 +25,7 @@ public partial class HitMarkerContainer : Container, IRequireHighFrequencyMouseP { private Vector2 lastMousePosition; private Vector2? lastlastMousePosition; - private double? timePreempt; - private double TimePreempt - { - get => timePreempt ?? default_time_preempt; - set => timePreempt = value; - } + private double timePreempt; public Bindable HitMarkerEnabled = new BindableBool(); public Bindable AimMarkersEnabled = new BindableBool(); @@ -45,6 +40,7 @@ private double TimePreempt public HitMarkerContainer(HitObjectContainer hitObjectContainer) { this.hitObjectContainer = hitObjectContainer; + timePreempt = default_time_preempt; } public bool OnPressed(KeyBindingPressEvent e) @@ -52,7 +48,7 @@ public bool OnPressed(KeyBindingPressEvent e) if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) { updateTimePreempt(); - AddMarker(e.Action); + addMarker(e.Action); } return false; @@ -70,33 +66,35 @@ protected override bool OnMouseMove(MouseMoveEvent e) if (AimMarkersEnabled.Value) { updateTimePreempt(); - AddMarker(null); + addMarker(null); } if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition) { if (!AimMarkersEnabled.Value) updateTimePreempt(); - Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt)); + Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, timePreempt)); } return base.OnMouseMove(e); } - private void AddMarker(OsuAction? action) + private void addMarker(OsuAction? action) { var component = OsuSkinComponents.AimMarker; - switch(action) + + switch (action) { case OsuAction.LeftButton: component = OsuSkinComponents.HitMarkerLeft; break; + case OsuAction.RightButton: component = OsuSkinComponents.HitMarkerRight; break; } - Add(new HitMarkerDrawable(action, component, TimePreempt) + Add(new HitMarkerDrawable(action, component, timePreempt) { Position = lastMousePosition, Origin = Anchor.Centre, @@ -110,13 +108,14 @@ private void updateTimePreempt() if (hitObject == null) return; - TimePreempt = hitObject.TimePreempt; + timePreempt = hitObject.TimePreempt; } private OsuHitObject? getHitObject() { foreach (var dho in hitObjectContainer.Objects) return (dho as DrawableOsuHitObject)?.HitObject; + return null; } @@ -141,7 +140,7 @@ protected override void LoadComplete() LifetimeStart = Time.Current; LifetimeEnd = LifetimeStart + lifetimeDuration; - Scheduler.AddDelayed(() => + Scheduler.AddDelayed(() => { this.FadeOut(fadeOutTime); }, lifetimeDuration - fadeOutTime); @@ -186,11 +185,11 @@ protected override void LoadComplete() LifetimeStart = Time.Current; LifetimeEnd = LifetimeStart + lifetimeDuration; - Scheduler.AddDelayed(() => + Scheduler.AddDelayed(() => { this.FadeOut(fadeOutTime); }, lifetimeDuration - fadeOutTime); } } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 319ae3e1f5a4..51fd835dc502 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -1,14 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; using osu.Game.Localisation; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.PlayerSettings; @@ -16,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { - protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset; + protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset; private readonly PlayerCheckbox hitMarkerToggle; private readonly PlayerCheckbox aimMarkerToggle; @@ -37,9 +32,9 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) protected override void LoadComplete() { - drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); - drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); - drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); + DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); + DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + DrawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); hideCursorToggle.Current.BindValueChanged(onCursorToggle); } @@ -48,11 +43,12 @@ private void onCursorToggle(ValueChangedEvent hide) // this only hides half the cursor if (hide.NewValue) { - drawableRuleset.Playfield.Cursor.FadeOut(); - } else + DrawableRuleset.Playfield.Cursor.FadeOut(); + } + else { - drawableRuleset.Playfield.Cursor.FadeIn(); + DrawableRuleset.Playfield.Cursor.FadeIn(); } } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 5fbb23f094c6..84177f26b07d 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -403,7 +403,7 @@ protected Ruleset() /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. /// public virtual DifficultySection? CreateEditorDifficultySection() => null; - + public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0fa714369354..ad1f9ec8979e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -115,7 +115,7 @@ public abstract partial class Player : ScreenWithBeatmapBackground, ISamplePlayb public GameplayState GameplayState { get; private set; } - protected Ruleset ruleset; + private Ruleset ruleset; public BreakOverlay BreakOverlay; diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index 122ca291428d..e30d2b2d4285 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -7,12 +7,12 @@ namespace osu.Game.Screens.Play.PlayerSettings { public partial class AnalysisSettings : PlayerSettingsGroup { - protected DrawableRuleset drawableRuleset; + protected DrawableRuleset DrawableRuleset; public AnalysisSettings(DrawableRuleset drawableRuleset) : base("Analysis Settings") { - this.drawableRuleset = drawableRuleset; + DrawableRuleset = drawableRuleset; } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 0d877785e7fc..65e99731dc23 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -72,10 +72,9 @@ private void load(OsuConfigManager config) HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - var analysisSettings = ruleset.CreateAnalysisSettings(DrawableRuleset); - if (analysisSettings != null) { + var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset); + if (analysisSettings != null) HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); - } } protected override void PrepareReplay() From 2a1fa8ccacd0ece5f6a098bd4951cefcd9a862c7 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Thu, 22 Feb 2024 22:34:38 -0500 Subject: [PATCH 024/579] fix OsuAnalysisSettings text not updating --- osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 51fd835dc502..0411882d2aad 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Localisation; @@ -30,7 +31,8 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) }; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); From 4d669c56a2adb102031e1ea0c63538409ace3ba8 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Fri, 23 Feb 2024 23:32:35 -0500 Subject: [PATCH 025/579] implement pooling Uses pooling for all analysis objects and creates the lifetime entries from replay data when the analysis container is constructed. --- .../UI/OsuAnalysisContainer.cs | 242 ++++++++++++++++++ .../UI/OsuAnalysisSettings.cs | 18 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 5 +- osu.Game/Rulesets/UI/AnalysisContainer.cs | 18 ++ osu.Game/Rulesets/UI/Playfield.cs | 6 + .../Play/PlayerSettings/AnalysisSettings.cs | 5 +- osu.Game/Screens/Play/ReplayPlayer.cs | 3 + 7 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs create mode 100644 osu.Game/Rulesets/UI/AnalysisContainer.cs diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs new file mode 100644 index 000000000000..a637eddd05e5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -0,0 +1,242 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Pooling; +using osu.Game.Replays; +using osu.Game.Rulesets.Objects.Pooling; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI +{ + public partial class OsuAnalysisContainer : AnalysisContainer + { + public Bindable HitMarkerEnabled = new BindableBool(); + public Bindable AimMarkersEnabled = new BindableBool(); + public Bindable AimLinesEnabled = new BindableBool(); + + private HitMarkersContainer hitMarkersContainer; + private AimMarkersContainer aimMarkersContainer; + private AimLinesContainer aimLinesContainer; + + public OsuAnalysisContainer(Replay replay) + : base(replay) + { + InternalChildren = new Drawable[] + { + hitMarkersContainer = new HitMarkersContainer(), + aimMarkersContainer = new AimMarkersContainer() { Depth = float.MinValue }, + aimLinesContainer = new AimLinesContainer() { Depth = float.MaxValue } + }; + + HitMarkerEnabled.ValueChanged += e => hitMarkersContainer.FadeTo(e.NewValue ? 1 : 0); + AimMarkersEnabled.ValueChanged += e => aimMarkersContainer.FadeTo(e.NewValue ? 1 : 0); + AimLinesEnabled.ValueChanged += e => aimLinesContainer.FadeTo(e.NewValue ? 1 : 0); + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + var aimLineColor = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; + aimLineColor.A = 127; + + hitMarkersContainer.Hide(); + aimMarkersContainer.Hide(); + aimLinesContainer.Hide(); + + bool leftHeld = false; + bool rightHeld = false; + foreach (var frame in Replay.Frames) + { + var osuFrame = (OsuReplayFrame)frame; + + aimMarkersContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + aimLinesContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + + bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); + bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); + + if (leftHeld && !leftButton) + leftHeld = false; + else if (!leftHeld && leftButton) + { + hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); + leftHeld = true; + } + + if (rightHeld && !rightButton) + rightHeld = false; + else if (!rightHeld && rightButton) + { + hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); + rightHeld = true; + } + } + } + + private partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer + { + private readonly HitMarkerPool leftPool; + private readonly HitMarkerPool rightPool; + + public HitMarkersContainer() + { + AddInternal(leftPool = new HitMarkerPool(OsuSkinComponents.HitMarkerLeft, OsuAction.LeftButton, 15)); + AddInternal(rightPool = new HitMarkerPool(OsuSkinComponents.HitMarkerRight, OsuAction.RightButton, 15)); + } + + protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry)); + } + + private partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer + { + private readonly HitMarkerPool pool; + + public AimMarkersContainer() + { + AddInternal(pool = new HitMarkerPool(OsuSkinComponents.AimMarker, null, 80)); + } + + protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); + } + + private partial class AimLinesContainer : Path + { + private LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); + + public AimLinesContainer() + { + lifetimeManager.EntryBecameAlive += entryBecameAlive; + lifetimeManager.EntryBecameDead += entryBecameDead; + lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; + + PathRadius = 1f; + Colour = new Color4(255, 255, 255, 127); + } + + protected override void Update() + { + base.Update(); + + lifetimeManager.Update(Time.Current); + } + + public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); + + private void entryBecameAlive(LifetimeEntry entry) + { + aliveEntries.Add((AimPointEntry)entry); + updateVertices(); + } + + private void entryBecameDead(LifetimeEntry entry) + { + aliveEntries.Remove((AimPointEntry)entry); + updateVertices(); + } + + private void updateVertices() + { + ClearVertices(); + foreach (var entry in aliveEntries) + { + AddVertex(entry.Position); + } + } + + private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) + { + + } + + private sealed class AimLinePointComparator : IComparer + { + public int Compare(AimPointEntry? x, AimPointEntry? y) + { + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); + + return x.LifetimeStart.CompareTo(y.LifetimeStart); + } + } + } + + private partial class HitMarkerDrawable : PoolableDrawableWithLifetime + { + /// + /// This constructor only exists to meet the new() type constraint of . + /// + public HitMarkerDrawable() + { + } + + public HitMarkerDrawable(OsuSkinComponents component, OsuAction? action) + { + Origin = Anchor.Centre; + InternalChild = new SkinnableDrawable(new OsuSkinComponentLookup(component), _ => new DefaultHitMarker(action)); + } + + protected override void OnApply(AimPointEntry entry) + { + Position = entry.Position; + + using (BeginAbsoluteSequence(LifetimeStart)) + Show(); + + using (BeginAbsoluteSequence(LifetimeEnd - 200)) + this.FadeOut(200); + } + } + + private partial class HitMarkerPool : DrawablePool + { + private readonly OsuSkinComponents component; + private readonly OsuAction? action; + + public HitMarkerPool(OsuSkinComponents component, OsuAction? action, int initialSize) + : base(initialSize) + { + this.component = component; + this.action = action; + } + + protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(component, action); + } + + private partial class AimPointEntry : LifetimeEntry + { + public Vector2 Position { get; } + + public AimPointEntry(double time, Vector2 position) + { + LifetimeStart = time; + LifetimeEnd = time + 1_000; + Position = position; + } + } + + private partial class HitMarkerEntry : AimPointEntry + { + public bool IsLeftMarker { get; } + + public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker) + : base(lifetimeStart, position) + { + IsLeftMarker = isLeftMarker; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 0411882d2aad..fd9cb6799577 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Localisation; +using osu.Game.Replays; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.PlayerSettings; @@ -29,14 +29,7 @@ public OsuAnalysisSettings(DrawableRuleset drawableRuleset) aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } }; - } - [BackgroundDependencyLoader] - private void load() - { - DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); - DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); - DrawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); hideCursorToggle.Current.BindValueChanged(onCursorToggle); } @@ -52,5 +45,14 @@ private void onCursorToggle(ValueChangedEvent hide) DrawableRuleset.Playfield.Cursor.FadeIn(); } } + + public override AnalysisContainer CreateAnalysisContainer(Replay replay) + { + var analysisContainer = new OsuAnalysisContainer(replay); + analysisContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); + analysisContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + analysisContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); + return analysisContainer; + } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 3cb3c50ef722..ea336e6067c0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -37,8 +37,6 @@ public partial class OsuPlayfield : Playfield public SmokeContainer Smoke { get; } - public HitMarkerContainer MarkersContainer { get; } - public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -61,8 +59,7 @@ public OsuPlayfield() judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, - approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both } + approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both } }; HitPolicy = new StartTimeOrderedHitPolicy(); diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs new file mode 100644 index 000000000000..62d54374e724 --- /dev/null +++ b/osu.Game/Rulesets/UI/AnalysisContainer.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Game.Replays; + +namespace osu.Game.Rulesets.UI +{ + public partial class AnalysisContainer : Container + { + protected Replay Replay; + + public AnalysisContainer(Replay replay) + { + Replay = replay; + } + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 90a2f63faa8a..e116acdc1915 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -291,6 +291,12 @@ protected override void Update() /// protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); + /// + /// Adds an analysis container to internal children for replays. + /// + /// + public virtual void AddAnalysisContainer(AnalysisContainer analysisContainer) => AddInternal(analysisContainer); + #region Pooling support private readonly Dictionary pools = new Dictionary(); diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index e30d2b2d4285..3752b2b900ac 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -1,11 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Replays; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.PlayerSettings { - public partial class AnalysisSettings : PlayerSettingsGroup + public abstract partial class AnalysisSettings : PlayerSettingsGroup { protected DrawableRuleset DrawableRuleset; @@ -14,5 +15,7 @@ public AnalysisSettings(DrawableRuleset drawableRuleset) { DrawableRuleset = drawableRuleset; } + + public abstract AnalysisContainer CreateAnalysisContainer(Replay replay); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 65e99731dc23..ce6cb5124a61 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -74,7 +74,10 @@ private void load(OsuConfigManager config) var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset); if (analysisSettings != null) + { HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); + DrawableRuleset.Playfield.AddAnalysisContainer(analysisSettings.CreateAnalysisContainer(GameplayState.Score.Replay)); + } } protected override void PrepareReplay() From c95e85368fab71e8c38e10f3dcf809f47f0986e3 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Fri, 23 Feb 2024 23:45:58 -0500 Subject: [PATCH 026/579] remove old files --- .../TestSceneHitMarker.cs | 135 ------------ .../UI/HitMarkerContainer.cs | 195 ------------------ 2 files changed, 330 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs delete mode 100644 osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs deleted file mode 100644 index 7ba681b50f1d..000000000000 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs +++ /dev/null @@ -1,135 +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 System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Input.Events; -using osu.Framework.Input.States; -using osu.Framework.Logging; -using osu.Framework.Testing.Input; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Tests -{ - public partial class TestSceneHitMarker : OsuSkinnableTestScene - { - [Test] - public void TestHitMarkers() - { - var markerContainers = new List(); - - AddStep("Create hit markers", () => - { - markerContainers.Clear(); - SetContents(_ => - { - markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer()) - { - HitMarkerEnabled = { Value = true }, - AimMarkersEnabled = { Value = true }, - AimLinesEnabled = { Value = true }, - RelativeSizeAxes = Axes.Both - }); - - return new HitMarkerInputManager - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.95f), - Child = markerContainers[^1], - }; - }); - }); - - AddUntilStep("Until skinnable expires", () => - { - if (markerContainers.Count == 0) - return false; - - Logger.Log("How many: " + markerContainers.Count); - - foreach (var markerContainer in markerContainers) - { - if (markerContainer.Children.Count != 0) - return false; - } - - return true; - }); - } - - private partial class HitMarkerInputManager : ManualInputManager - { - private double? startTime; - - public HitMarkerInputManager() - { - UseParentInput = false; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - MoveMouseTo(ToScreenSpace(DrawSize / 2)); - } - - protected override void Update() - { - base.Update(); - - const float spin_angle = 4 * MathF.PI; - - startTime ??= Time.Current; - - float fraction = (float)((Time.Current - startTime) / 5_000); - - float angle = fraction * spin_angle; - float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2; - - Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2; - MoveMouseTo(ToScreenSpace(pos)); - } - } - - private partial class TestHitMarkerContainer : HitMarkerContainer - { - private double? lastClick; - private double? startTime; - private bool finishedDrawing; - private bool leftOrRight; - - public TestHitMarkerContainer(HitObjectContainer hitObjectContainer) - : base(hitObjectContainer) - { - } - - protected override void Update() - { - base.Update(); - - if (finishedDrawing) - return; - - startTime ??= lastClick ??= Time.Current; - - if (startTime + 5_000 <= Time.Current) - { - finishedDrawing = true; - HitMarkerEnabled.Value = AimMarkersEnabled.Value = AimLinesEnabled.Value = false; - return; - } - - if (lastClick + 400 <= Time.Current) - { - OnPressed(new KeyBindingPressEvent(new InputState(), leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton)); - leftOrRight = !leftOrRight; - lastClick = Time.Current; - } - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs deleted file mode 100644 index e8916ea5451a..000000000000 --- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs +++ /dev/null @@ -1,195 +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 System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Skinning; -using osu.Game.Rulesets.Osu.Skinning.Default; -using osu.Game.Rulesets.UI; -using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu.UI -{ - public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler - { - private Vector2 lastMousePosition; - private Vector2? lastlastMousePosition; - private double timePreempt; - - public Bindable HitMarkerEnabled = new BindableBool(); - public Bindable AimMarkersEnabled = new BindableBool(); - public Bindable AimLinesEnabled = new BindableBool(); - - private const double default_time_preempt = 1000; - - private readonly HitObjectContainer hitObjectContainer; - - public override bool ReceivePositionalInputAt(Vector2 _) => true; - - public HitMarkerContainer(HitObjectContainer hitObjectContainer) - { - this.hitObjectContainer = hitObjectContainer; - timePreempt = default_time_preempt; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) - { - updateTimePreempt(); - addMarker(e.Action); - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - lastlastMousePosition = lastMousePosition; - lastMousePosition = e.MousePosition; - - if (AimMarkersEnabled.Value) - { - updateTimePreempt(); - addMarker(null); - } - - if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition) - { - if (!AimMarkersEnabled.Value) - updateTimePreempt(); - Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, timePreempt)); - } - - return base.OnMouseMove(e); - } - - private void addMarker(OsuAction? action) - { - var component = OsuSkinComponents.AimMarker; - - switch (action) - { - case OsuAction.LeftButton: - component = OsuSkinComponents.HitMarkerLeft; - break; - - case OsuAction.RightButton: - component = OsuSkinComponents.HitMarkerRight; - break; - } - - Add(new HitMarkerDrawable(action, component, timePreempt) - { - Position = lastMousePosition, - Origin = Anchor.Centre, - Depth = action == null ? float.MaxValue : float.MinValue - }); - } - - private void updateTimePreempt() - { - var hitObject = getHitObject(); - if (hitObject == null) - return; - - timePreempt = hitObject.TimePreempt; - } - - private OsuHitObject? getHitObject() - { - foreach (var dho in hitObjectContainer.Objects) - return (dho as DrawableOsuHitObject)?.HitObject; - - return null; - } - - private partial class HitMarkerDrawable : SkinnableDrawable - { - private readonly double lifetimeDuration; - private readonly double fadeOutTime; - - public override bool RemoveWhenNotAlive => true; - - public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt) - : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action)) - { - fadeOutTime = timePreempt / 2; - lifetimeDuration = timePreempt + fadeOutTime; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - LifetimeStart = Time.Current; - LifetimeEnd = LifetimeStart + lifetimeDuration; - - Scheduler.AddDelayed(() => - { - this.FadeOut(fadeOutTime); - }, lifetimeDuration - fadeOutTime); - } - } - - private partial class AimLineDrawable : CompositeDrawable - { - private readonly double lifetimeDuration; - private readonly double fadeOutTime; - - public override bool RemoveWhenNotAlive => true; - - public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt) - { - fadeOutTime = timePreempt / 2; - lifetimeDuration = timePreempt + fadeOutTime; - - float distance = Vector2.Distance(fromP, toP); - Vector2 direction = (toP - fromP); - InternalChild = new Box - { - Position = fromP + (direction / 2), - Size = new Vector2(distance, 1), - Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)), - Origin = Anchor.Centre - }; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; - color.A = 127; - InternalChild.Colour = color; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - LifetimeStart = Time.Current; - LifetimeEnd = LifetimeStart + lifetimeDuration; - - Scheduler.AddDelayed(() => - { - this.FadeOut(fadeOutTime); - }, lifetimeDuration - fadeOutTime); - } - } - } -} From 8cdd9c9ddcec526dcf4866f2c0b0ce1b98dbffd4 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Fri, 23 Feb 2024 23:46:50 -0500 Subject: [PATCH 027/579] skinning changes improve default design --- .../Skinning/Default/DefaultHitMarker.cs | 8 +++----- osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs | 1 - osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs | 6 +----- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs index 7dabb5182f70..dc890d4d637d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs @@ -23,7 +23,6 @@ public DefaultHitMarker(OsuAction? action) Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(3, length), - Rotation = 45, Colour = Colour4.Black.Opacity(0.5F) }, new Box @@ -31,7 +30,7 @@ public DefaultHitMarker(OsuAction? action) Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(3, length), - Rotation = 135, + Rotation = 90, Colour = Colour4.Black.Opacity(0.5F) } }; @@ -44,7 +43,6 @@ public DefaultHitMarker(OsuAction? action) Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(1, length), - Rotation = 45, Colour = colour }, new Box @@ -52,7 +50,7 @@ public DefaultHitMarker(OsuAction? action) Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(1, length), - Rotation = 135, + Rotation = 90, Colour = colour } }); @@ -69,7 +67,7 @@ public DefaultHitMarker(OsuAction? action) return (Colour4.LightGreen, 20, true); default: - return (Colour4.Gray.Opacity(0.3F), 8, false); + return (Colour4.Gray.Opacity(0.5F), 8, false); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index 5c864fb6c21a..24f9217a5f45 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -10,6 +10,5 @@ public enum OsuSkinColour SliderBall, SpinnerBackground, StarBreakAdditive, - ReplayAimLine, } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index a637eddd05e5..68686f835d0d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -12,7 +12,6 @@ using osu.Game.Replays; using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.UI; using osu.Game.Skinning; @@ -47,11 +46,8 @@ public OsuAnalysisContainer(Replay replay) } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load() { - var aimLineColor = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; - aimLineColor.A = 127; - hitMarkersContainer.Hide(); aimMarkersContainer.Hide(); aimLinesContainer.Hide(); From 822ecb71068b77e071fb590983ff2834b0f9b2e3 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Fri, 23 Feb 2024 23:52:17 -0500 Subject: [PATCH 028/579] remove unnecessary changes --- osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini | 5 +---- osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs | 6 ------ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini index 2952948f452a..9d16267d733b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini @@ -1,7 +1,4 @@ [General] Version: latest HitCircleOverlayAboveNumber: 0 -HitCirclePrefix: display - -[Colours] -ReplayAimLine: 0,0,255 \ No newline at end of file +HitCirclePrefix: display \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 68686f835d0d..c4b5135ca2e4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -117,7 +117,6 @@ public AimLinesContainer() { lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; - lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; PathRadius = 1f; Colour = new Color4(255, 255, 255, 127); @@ -153,11 +152,6 @@ private void updateVertices() } } - private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) - { - - } - private sealed class AimLinePointComparator : IComparer { public int Compare(AimPointEntry? x, AimPointEntry? y) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ea336e6067c0..a801e3d2f7aa 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -59,7 +59,7 @@ public OsuPlayfield() judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, - approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both } + approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; HitPolicy = new StartTimeOrderedHitPolicy(); From 29e5f409bac928bc49e1940a2e44b8ee4b8f2a70 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Tue, 27 Feb 2024 21:51:54 -0500 Subject: [PATCH 029/579] remove skinnable better to add skinnable elements later --- .../Resources/special-skin/aimmarker@2x.png | Bin 648 -> 0 bytes .../special-skin/hitmarker-left@2x.png | Bin 2127 -> 0 bytes .../special-skin/hitmarker-right@2x.png | Bin 1742 -> 0 bytes osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 - .../Skinning/Default/DefaultHitMarker.cs | 74 ------------------ .../Skinning/Default/HitMarker.cs | 68 ++++++++++++---- .../Legacy/OsuLegacySkinTransformer.cs | 20 +---- .../UI/OsuAnalysisContainer.cs | 69 ++++++++-------- 8 files changed, 88 insertions(+), 146 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-left@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png deleted file mode 100644 index 0b2a554193f08f7984568980956a3db8a6b39800..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmV;30(bq1P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a6PO$WR@`E-K4rtTK|H%@ z>74h8L#!+*#OK7523?T&k?XR{Z=6dG3p_JqWYhD+A!4!A#c~(3vY`^s5JwbMqkJLf zvch?bvs$gQ_C5Ivg9U9R!*!aYNMH#`q#!~@9TikzAxf)8iitGs$36Tbjz2{%nOqex zax9<*6_Voz|AXJ%n#JiUHz^ngdS7h&V+;uF0jdyW16NwdUuyz$pQJZB zTI2{A+y*YLJDR))TI00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=mHiD95ZRmtwR6+02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{003Y~L_t&-(~Xd^4Zt7_1kYC1UL}8=Py}}=u;}y?!uSxX)0000EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000IiNkls_o@u5n{w3H!5jp#I_s}mSL6nvbfkWc#_vuLYo^#J%=bYz0&vPDu>`?~P z0Nc!V3E&1C=JO;l4U7YWzyQ!=wjUbdA^U#|Xawp!3a-6gDOj^!(!4I&S*VDQsaC;d zkpu@oS~HUIlo5?2^x2VUX1*t+Nq-yB9v@8*1=@kX09PKhkXqoU8}iuo%6H`9I*-*= zs7QtWsq|QHIFq^%*3_$0#@g!%TuB`Tz#)<-Rfv`s2s4$%QoO2IwpJ8aHbxR!qW7trJguJ@1?U45bE-`_uOK{-b-%3G@PfU<~*e zh?{*?U>&d#r~s;f+WnivkpmT$8`bNrWoNG~1b*F}+HfgG2XGX)$`bIrTV}Ye*3uC> zq=ruJv2<)!2?D2qeSqx&#}JNi2t zZQa#wVF)+@__FvNA8?}DEev%w+PVYHj{b&HVE||~=kU;$=+xmIQrE;mC2+(ioi{B_ z14~B(&~wBmou4?U1PDHZc=jd~eOHCfJ4>$%CvGf!H$C^B1-{^CW zORYQQPIC*FJ;-)C)w)yeyz;EQuf9aM2(<9%X{j#}E?#-o-e-zAb+2tE-D7|^5ATq? zKPt;x^IFGEMO1bn`Y0b*rGQ)vpN56TnkFjz%cEl&04>UXaP19WdWiP+eR%_|# zY_xTo)~RR(2`K`4IvZ`>ZMBvT;GKKA9b5Amiycx!=6|Arl}AIhTNsKRQbSww88cm_ z&$%e?{q<>UQ8Hr~O=r?UpqZ7)iIaOQk2_>R_~GAElfeGZc(EJun2f*VoHpGKA1fE% zW|d(4CFk^pJSI&K{I>Z$^s8!FUC@l#qnEW&;P)$7NN6_2le@kqBsYFlnE6LgSAh=E zd{|fKvAT}?({|u}RzB|^_owZ39;*weyX}g26oT_FIwQc`1A4KK8XGV-|DrSEQ3wKM zB2cr}D+T>i=`k~&xS0b&ZUX20Q|Yn2UMUFFh`_d*^^(>b&ZNwsC|Bt14QEm{>m?1? zCIV$%m+ZU{)>JdH%N6_=!kX%J$xfh521*JQQMx*17-o2yD~w&8GS(IB2Y0l0XL0O! zQb(~!i_VG2DnSO4Y0bbPVkC7`;L~hoUhdplS)RM<5J{vpdqa)piM1;R`uq0a*2A}}}-&CFL8OK}!6 zfVo0NWw8_=iDu@3K@k|}329PQv20~AjhQP{RTazDo{%Q7nAu-FPUGNcf@mb6MfPtJ zM}Ybq5K_N?lQfP9prGz^zLl@R8mKqoXdRswQBoUra#pR{83{q(7nteTADoK?pG`A`9D8* zL+g+7N8rOR69RB?D8@HbQD5`&4x1ww%{(zYhq8?E{44b(!oD|l*)8v0UWm1Qq+bAj zbN5U4x*zH93LD?uTB69FHx-%Cyv%2>X8fJ)3^^T6(_aU)m&#*Br_F z6_Nx3XU28k6Kk-%u-)ePB(8b=QDY16?Y- z$_egQ2*3<*<;=LgJzv(Xzwp`DthMUSb0Oo$z$d`aQkhub=n{^MKyPm^JPw7#IhEi{TDHR{#GTxr2YT^002ovPDHLk FV1jr`?veli diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png deleted file mode 100644 index 66be9a80ee2e2ae4c6dc6915d2b7ca5cf8ab589f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1742 zcmV;<1~K`GP)EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000D~Nkl1L9Yb|fH_ftE7RjjO?hFdLVhHsD6;Mi3NQZON`8V+<3BQA3EPF^Lyy zBFH5ssJYD~PVPNjt#956lhOc~_xg%spW#euk$w}6}R9gK=vhuEEC9o1$c~`Rf z(Ry2wlf>QYeKL21@Rg4>x`?ys*smS!IAyn7e8E7rV7}_2!Kl|YJi?#x1%84Syp!In zkY2Z>ZC|ASeH8V&R&~)}wqQQKV8CuUoJ!|IpZh27F_`+c=GVn>f!toVMTXssZ0rPq-2F z7)4*YhF6Y6=fW|AnK={TxGQO->3NOv?ZHqu?n-9PL^x&;=hC%on8f+eXCO0UEIMoi zC$QrUyo)<T*3ZV=R?f-~Sw)q8_7}ITNXzj(Ynz8XY#0nKO|Ffd3lJ+SQw? zo^T_5u}|1KI1!G!Qa#~D8k*bxlSMfdpVSn!s$M%F!q0GjixAm?KUED#k}2O6e=Ub! zzT9@~mdieLFWo14Y(4bY?{@Z~d#M9B__8N*HojnoWl8a{S^UD*#Oe5q57df^KXo6y zFS^)9_p=4_sqNTj>tdf)v)O`qjqaxoT%0!kCVZ*Rs)f?;&EU6Nn8-Z~eiR+BtjUAq zuj+95Y2#Stj`7sR9`G*v)Lv2)QZ=X0g)O!$}Y)kHjB1^&ZygL zaa;~xW2Fp;tl;b7xLnk27M%fmLZ@PB*b@vZ9}Jg6605gAXe zstYZ)p{)uZh6ZtROM+Y(5y>UDycZfo_&zMvtXfj*ar#}oX-JZ!IZ2XB>92W{iM*rM zPniAbKjTyQRE^sNDlJm64K~qIM5Tc?-B3Fj<2yzt(TD^gFHQ4B!vebI{%dk!P-X kiyU)`Hc@tO_2Ah*0YN2jCf7K0Z2$lO07*qoM6N<$f~iM7hyVZp diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 75db18d345e9..52fdfea95f96 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -22,8 +22,5 @@ public enum OsuSkinComponents SpinnerBody, CursorSmoke, ApproachCircle, - HitMarkerLeft, - HitMarkerRight, - AimMarker } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs deleted file mode 100644 index dc890d4d637d..000000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Skinning.Default -{ - public partial class DefaultHitMarker : CompositeDrawable - { - public DefaultHitMarker(OsuAction? action) - { - var (colour, length, hasBorder) = getConfig(action); - - if (hasBorder) - { - InternalChildren = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) - } - }; - } - - AddRangeInternal(new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Colour = colour - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - Colour = colour - } - }); - } - - private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) - { - switch (action) - { - case OsuAction.LeftButton: - return (Colour4.Orange, 20, true); - - case OsuAction.RightButton: - return (Colour4.LightGreen, 20, true); - - default: - return (Colour4.Gray.Opacity(0.5F), 8, false); - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs index 28877345d0b0..fe662470bcd0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs @@ -1,37 +1,73 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; -using osu.Game.Skinning; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public partial class HitMarker : Sprite + public partial class HitMarker : CompositeDrawable { - private readonly OsuAction? action; - - public HitMarker(OsuAction? action = null) + public HitMarker(OsuAction? action) { - this.action = action; + var (colour, length, hasBorder) = getConfig(action); + + if (hasBorder) + { + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 90, + Colour = Colour4.Black.Opacity(0.5F) + } + }; + } + + AddRangeInternal(new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Colour = colour + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 90, + Colour = colour + } + }); } - [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) { switch (action) { case OsuAction.LeftButton: - Texture = skin.GetTexture(@"hitmarker-left"); - break; + return (Colour4.Orange, 20, true); case OsuAction.RightButton: - Texture = skin.GetTexture(@"hitmarker-right"); - break; + return (Colour4.LightGreen, 20, true); default: - Texture = skin.GetTexture(@"aimmarker"); - break; + return (Colour4.Gray.Opacity(0.5F), 8, false); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 61f9eebd862f..d2ebc68c5225 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; -using osu.Game.Rulesets.Osu.Skinning.Default; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -169,23 +168,8 @@ public OsuLegacySkinTransformer(ISkin skin) return null; - case OsuSkinComponents.HitMarkerLeft: - if (GetTexture(@"hitmarker-left") != null) - return new HitMarker(OsuAction.LeftButton); - - return null; - - case OsuSkinComponents.HitMarkerRight: - if (GetTexture(@"hitmarker-right") != null) - return new HitMarker(OsuAction.RightButton); - - return null; - - case OsuSkinComponents.AimMarker: - if (GetTexture(@"aimmarker") != null) - return new HitMarker(); - - return null; + default: + throw new UnsupportedSkinComponentException(lookup); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index c4b5135ca2e4..7d8ae6980cda 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.UI; -using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -26,40 +25,41 @@ public partial class OsuAnalysisContainer : AnalysisContainer public Bindable AimMarkersEnabled = new BindableBool(); public Bindable AimLinesEnabled = new BindableBool(); - private HitMarkersContainer hitMarkersContainer; - private AimMarkersContainer aimMarkersContainer; - private AimLinesContainer aimLinesContainer; + protected HitMarkersContainer HitMarkers; + protected AimMarkersContainer AimMarkers; + protected AimLinesContainer AimLines; public OsuAnalysisContainer(Replay replay) : base(replay) { InternalChildren = new Drawable[] { - hitMarkersContainer = new HitMarkersContainer(), - aimMarkersContainer = new AimMarkersContainer() { Depth = float.MinValue }, - aimLinesContainer = new AimLinesContainer() { Depth = float.MaxValue } + HitMarkers = new HitMarkersContainer(), + AimMarkers = new AimMarkersContainer { Depth = float.MinValue }, + AimLines = new AimLinesContainer { Depth = float.MaxValue } }; - HitMarkerEnabled.ValueChanged += e => hitMarkersContainer.FadeTo(e.NewValue ? 1 : 0); - AimMarkersEnabled.ValueChanged += e => aimMarkersContainer.FadeTo(e.NewValue ? 1 : 0); - AimLinesEnabled.ValueChanged += e => aimLinesContainer.FadeTo(e.NewValue ? 1 : 0); + HitMarkerEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0); + AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0); + AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0); } [BackgroundDependencyLoader] private void load() { - hitMarkersContainer.Hide(); - aimMarkersContainer.Hide(); - aimLinesContainer.Hide(); + HitMarkers.Hide(); + AimMarkers.Hide(); + AimLines.Hide(); bool leftHeld = false; bool rightHeld = false; + foreach (var frame in Replay.Frames) { var osuFrame = (OsuReplayFrame)frame; - aimMarkersContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); - aimLinesContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + AimMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + AimLines.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); @@ -68,7 +68,7 @@ private void load() leftHeld = false; else if (!leftHeld && leftButton) { - hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); + HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); leftHeld = true; } @@ -76,42 +76,42 @@ private void load() rightHeld = false; else if (!rightHeld && rightButton) { - hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); + HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); rightHeld = true; } } } - private partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer + protected partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer { private readonly HitMarkerPool leftPool; private readonly HitMarkerPool rightPool; public HitMarkersContainer() { - AddInternal(leftPool = new HitMarkerPool(OsuSkinComponents.HitMarkerLeft, OsuAction.LeftButton, 15)); - AddInternal(rightPool = new HitMarkerPool(OsuSkinComponents.HitMarkerRight, OsuAction.RightButton, 15)); + AddInternal(leftPool = new HitMarkerPool(OsuAction.LeftButton, 15)); + AddInternal(rightPool = new HitMarkerPool(OsuAction.RightButton, 15)); } protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry)); } - private partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer + protected partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer { private readonly HitMarkerPool pool; public AimMarkersContainer() { - AddInternal(pool = new HitMarkerPool(OsuSkinComponents.AimMarker, null, 80)); + AddInternal(pool = new HitMarkerPool(null, 80)); } protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); } - private partial class AimLinesContainer : Path + protected partial class AimLinesContainer : Path { - private LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - private SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); public AimLinesContainer() { @@ -146,6 +146,7 @@ private void entryBecameDead(LifetimeEntry entry) private void updateVertices() { ClearVertices(); + foreach (var entry in aliveEntries) { AddVertex(entry.Position); @@ -164,7 +165,7 @@ public int Compare(AimPointEntry? x, AimPointEntry? y) } } - private partial class HitMarkerDrawable : PoolableDrawableWithLifetime + protected partial class HitMarkerDrawable : PoolableDrawableWithLifetime { /// /// This constructor only exists to meet the new() type constraint of . @@ -173,10 +174,10 @@ public HitMarkerDrawable() { } - public HitMarkerDrawable(OsuSkinComponents component, OsuAction? action) + public HitMarkerDrawable(OsuAction? action) { Origin = Anchor.Centre; - InternalChild = new SkinnableDrawable(new OsuSkinComponentLookup(component), _ => new DefaultHitMarker(action)); + InternalChild = new HitMarker(action); } protected override void OnApply(AimPointEntry entry) @@ -191,22 +192,20 @@ protected override void OnApply(AimPointEntry entry) } } - private partial class HitMarkerPool : DrawablePool + protected partial class HitMarkerPool : DrawablePool { - private readonly OsuSkinComponents component; private readonly OsuAction? action; - public HitMarkerPool(OsuSkinComponents component, OsuAction? action, int initialSize) + public HitMarkerPool(OsuAction? action, int initialSize) : base(initialSize) { - this.component = component; this.action = action; } - protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(component, action); + protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(action); } - private partial class AimPointEntry : LifetimeEntry + protected partial class AimPointEntry : LifetimeEntry { public Vector2 Position { get; } @@ -218,7 +217,7 @@ public AimPointEntry(double time, Vector2 position) } } - private partial class HitMarkerEntry : AimPointEntry + protected partial class HitMarkerEntry : AimPointEntry { public bool IsLeftMarker { get; } From cefc8357bbdb732f805e848b065afcf40619b9c0 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Tue, 27 Feb 2024 21:52:10 -0500 Subject: [PATCH 030/579] test scene for OsuAnalysisContainer --- .../TestSceneHitMarker.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs new file mode 100644 index 000000000000..e4c48f96b89d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs @@ -0,0 +1,91 @@ +// 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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneHitMarker : OsuTestScene + { + private TestOsuAnalysisContainer analysisContainer; + + [Test] + public void TestHitMarkers() + { + createAnalysisContainer(); + AddStep("enable hit markers", () => analysisContainer.HitMarkerEnabled.Value = true); + AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); + AddStep("disable hit markers", () => analysisContainer.HitMarkerEnabled.Value = false); + AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); + } + + [Test] + public void TestAimMarker() + { + createAnalysisContainer(); + AddStep("enable aim markers", () => analysisContainer.AimMarkersEnabled.Value = true); + AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); + AddStep("disable aim markers", () => analysisContainer.AimMarkersEnabled.Value = false); + AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); + } + + [Test] + public void TestAimLines() + { + createAnalysisContainer(); + AddStep("enable aim lines", () => analysisContainer.AimLinesEnabled.Value = true); + AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); + AddStep("disable aim lines", () => analysisContainer.AimLinesEnabled.Value = false); + AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); + } + + private void createAnalysisContainer() + { + AddStep("create new analysis container", () => Child = analysisContainer = new TestOsuAnalysisContainer(fabricateReplay())); + } + + private Replay fabricateReplay() + { + var frames = new List(); + + for (int i = 0; i < 50; i++) + { + frames.Add(new OsuReplayFrame + { + Time = Time.Current + i * 15, + Position = new Vector2(20 + i * 10, 20), + Actions = + { + i % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton + } + }); + } + + return new Replay { Frames = frames }; + } + + private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + { + public TestOsuAnalysisContainer(Replay replay) + : base(replay) + { + } + + public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); + + public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); + + public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; + } + } +} From 7687ab63edd2b49b4e1a5a2c30be2b9e2b8ca328 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Tue, 27 Feb 2024 22:03:45 -0500 Subject: [PATCH 031/579] fix code formatting --- osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 3 ++- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 - osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index fd9cb6799577..b3d5231adec4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -16,12 +16,13 @@ public partial class OsuAnalysisSettings : AnalysisSettings private readonly PlayerCheckbox hitMarkerToggle; private readonly PlayerCheckbox aimMarkerToggle; - private readonly PlayerCheckbox hideCursorToggle; private readonly PlayerCheckbox aimLinesToggle; public OsuAnalysisSettings(DrawableRuleset drawableRuleset) : base(drawableRuleset) { + PlayerCheckbox hideCursorToggle; + Children = new Drawable[] { hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index a801e3d2f7aa..411a02c5aff3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -36,7 +36,6 @@ public partial class OsuPlayfield : Playfield private readonly JudgementPooler judgementPooler; public SmokeContainer Smoke { get; } - public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index 3752b2b900ac..91531b28b407 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -10,7 +10,7 @@ public abstract partial class AnalysisSettings : PlayerSettingsGroup { protected DrawableRuleset DrawableRuleset; - public AnalysisSettings(DrawableRuleset drawableRuleset) + protected AnalysisSettings(DrawableRuleset drawableRuleset) : base("Analysis Settings") { DrawableRuleset = drawableRuleset; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index ce6cb5124a61..81cc44d889ba 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -73,6 +73,7 @@ private void load(OsuConfigManager config) HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset); + if (analysisSettings != null) { HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); From 203e9284eb5eb54d464e7a290e106b605b7cf66e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 19:01:53 +0200 Subject: [PATCH 032/579] End circle only gets brighter once shift is held --- .../Sliders/Components/SliderTailPiece.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index 5cf9346f2ebb..dc57d6d7ca1f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -61,11 +61,38 @@ protected override void OnHoverLost(HoverLostEvent e) updateCirclePieceColour(); } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat) + return false; + + handleDragToggle(e); + return base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyUpEvent e) + { + handleDragToggle(e); + base.OnKeyUp(e); + } + + private bool lastShiftPressed; + + private void handleDragToggle(KeyboardEvent key) + { + bool shiftPressed = key.ShiftPressed; + + if (shiftPressed == lastShiftPressed) return; + + lastShiftPressed = shiftPressed; + updateCirclePieceColour(); + } + private void updateCirclePieceColour() { Color4 colour = colours.Yellow; - if (IsHovered) + if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed) colour = colour.Lighten(1); CirclePiece.Colour = colour; From 7254096c9011132e68f9e1ef109a6f13c076b986 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 1 Jun 2024 20:28:39 +0200 Subject: [PATCH 033/579] fix isDragging being always true --- .../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index dc57d6d7ca1f..b5894eb84f15 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -129,10 +129,10 @@ protected override void OnDrag(DragEvent e) protected override void OnDragEnd(DragEndEvent e) { - if (isDragging) - { - editorBeatmap?.EndChange(); - } + if (!isDragging) return; + + isDragging = false; + editorBeatmap?.EndChange(); } /// From ca41c84ba21ab50aea0e9c1fef406c7bc26293a5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 1 Jun 2024 21:15:54 +0200 Subject: [PATCH 034/579] trim excess control points on drag end --- .../Sliders/Components/SliderTailPiece.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index b5894eb84f15..a0f4401ca73b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -131,10 +131,39 @@ protected override void OnDragEnd(DragEndEvent e) { if (!isDragging) return; + trimExcessControlPoints(Slider.Path); + isDragging = false; editorBeatmap?.EndChange(); } + /// + /// Trims control points from the end of the slider path which are not required to reach the expected end of the slider. + /// + /// The slider path to trim control points of. + private void trimExcessControlPoints(SliderPath sliderPath) + { + if (!sliderPath.ExpectedDistance.Value.HasValue) + return; + + double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray(); + int segmentIndex = 0; + + for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++) + { + if (!sliderPath.ControlPoints[i].Type.HasValue) continue; + + if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3)) + { + sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1); + sliderPath.ControlPoints[^1].Type = null; + break; + } + + segmentIndex++; + } + } + /// /// Finds the expected distance value for which the slider end is closest to the mouse position. /// From bb38cb4137b6bbfa87f152bcc43b5b470736c806 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 3 Jun 2024 13:18:36 +0200 Subject: [PATCH 035/579] simplify tracking changes in shift key status --- .../Sliders/Components/SliderTailPiece.cs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index a0f4401ca73b..f28a8fafdad4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -63,29 +63,18 @@ protected override void OnHoverLost(HoverLostEvent e) protected override bool OnKeyDown(KeyDownEvent e) { - if (e.Repeat) - return false; + if (!e.Repeat && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + updateCirclePieceColour(); - handleDragToggle(e); return base.OnKeyDown(e); } protected override void OnKeyUp(KeyUpEvent e) { - handleDragToggle(e); - base.OnKeyUp(e); - } + if (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight) + updateCirclePieceColour(); - private bool lastShiftPressed; - - private void handleDragToggle(KeyboardEvent key) - { - bool shiftPressed = key.ShiftPressed; - - if (shiftPressed == lastShiftPressed) return; - - lastShiftPressed = shiftPressed; - updateCirclePieceColour(); + base.OnKeyUp(e); } private void updateCirclePieceColour() From 77b47ad2b4a3ea402017c48cb04b095dc1bd1b43 Mon Sep 17 00:00:00 2001 From: Olivier Schipper Date: Mon, 3 Jun 2024 13:23:39 +0200 Subject: [PATCH 036/579] simplify nullability annotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index a0f4401ca73b..23ebd9248231 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -29,7 +29,7 @@ public partial class SliderTailPiece : SliderCircleOverlay private readonly Cached fullPathCache = new Cached(); - [Resolved(CanBeNull = true)] + [Resolved] private EditorBeatmap? editorBeatmap { get; set; } [Resolved] From 34c4ee7de8ea1b2228221c46aea2acb2fac56afd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 3 Jun 2024 13:38:42 +0200 Subject: [PATCH 037/579] add CanBeNull attribute to LastRepeat --- .../Edit/Blueprints/Sliders/SliderCircleOverlay.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index 2bf5118039a1..55ea131dab2e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -33,7 +33,7 @@ protected override void Update() base.Update(); var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : - Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat; + Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat!; CirclePiece.UpdateFrom(circle); marker.UpdateFrom(circle); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 041907d790ac..57f277e1ce5c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Objects; using System.Linq; using System.Threading; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -163,6 +164,7 @@ public double SliderVelocityMultiplier public SliderTailCircle TailCircle { get; protected set; } [JsonIgnore] + [CanBeNull] public SliderRepeat LastRepeat { get; protected set; } public Slider() From 82919998dac6d38de78a4c2041e068e2a09461c0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 4 Jun 2024 18:26:32 +0200 Subject: [PATCH 038/579] dont light up tail piece when hovering anchor --- .../Sliders/Components/SliderTailPiece.cs | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index cf8f909ff6ba..7d39f045968a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -50,38 +50,18 @@ protected override void LoadComplete() public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos); - protected override bool OnHover(HoverEvent e) + protected override void Update() { updateCirclePieceColour(); - return false; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateCirclePieceColour(); - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (!e.Repeat && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) - updateCirclePieceColour(); - - return base.OnKeyDown(e); - } - - protected override void OnKeyUp(KeyUpEvent e) - { - if (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight) - updateCirclePieceColour(); - - base.OnKeyUp(e); + base.Update(); } private void updateCirclePieceColour() { Color4 colour = colours.Yellow; - if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed) + if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed + && !inputManager.HoveredDrawables.Any(o => o is PathControlPointPiece)) colour = colour.Lighten(1); CirclePiece.Colour = colour; From efc8e1431a11a5e6e468ba72a30411b112d1c850 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 19 Jun 2024 23:15:35 +0200 Subject: [PATCH 039/579] activate length change with context menu --- .../Blueprints/Sliders/Components/SliderTailPiece.cs | 10 ++++++++-- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs index 7d39f045968a..2ebdf8760612 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs @@ -20,6 +20,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public partial class SliderTailPiece : SliderCircleOverlay { + /// + /// Whether this slider tail is draggable, changing the distance of the slider. + /// + public bool IsDraggable { get; set; } + /// /// Whether this is currently being dragged. /// @@ -60,7 +65,7 @@ private void updateCirclePieceColour() { Color4 colour = colours.Yellow; - if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed + if (IsHovered && IsDraggable && !inputManager.HoveredDrawables.Any(o => o is PathControlPointPiece)) colour = colour.Lighten(1); @@ -69,7 +74,7 @@ private void updateCirclePieceColour() protected override bool OnDragStart(DragStartEvent e) { - if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed) + if (e.Button == MouseButton.Right || !IsDraggable) return false; isDragging = true; @@ -103,6 +108,7 @@ protected override void OnDragEnd(DragEndEvent e) trimExcessControlPoints(Slider.Path); isDragging = false; + IsDraggable = false; editorBeatmap?.EndChange(); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 2c239a40c8e8..4a949f5b48ee 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -409,6 +409,10 @@ private void convertToStream() addControlPoint(rightClickPosition); changeHandler?.EndChange(); }), + new OsuMenuItem("Adjust distance", MenuItemType.Standard, () => + { + TailPiece.IsDraggable = true; + }), new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream), }; From b24bfa290806117753dc1e7969ddf3966d09094e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 20 Jun 2024 00:02:43 +0200 Subject: [PATCH 040/579] click to choose length instead of drag --- .../Sliders/Components/SliderTailPiece.cs | 185 ------------------ .../Sliders/SliderSelectionBlueprint.cs | 129 +++++++++++- 2 files changed, 124 insertions(+), 190 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs deleted file mode 100644 index 2ebdf8760612..000000000000 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs +++ /dev/null @@ -1,185 +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 System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Caching; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Input; -using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; - -namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components -{ - public partial class SliderTailPiece : SliderCircleOverlay - { - /// - /// Whether this slider tail is draggable, changing the distance of the slider. - /// - public bool IsDraggable { get; set; } - - /// - /// Whether this is currently being dragged. - /// - private bool isDragging; - - private InputManager inputManager = null!; - - private readonly Cached fullPathCache = new Cached(); - - [Resolved] - private EditorBeatmap? editorBeatmap { get; set; } - - [Resolved] - private OsuColour colours { get; set; } = null!; - - public SliderTailPiece(Slider slider, SliderPosition position) - : base(slider, position) - { - Slider.Path.ControlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos); - - protected override void Update() - { - updateCirclePieceColour(); - base.Update(); - } - - private void updateCirclePieceColour() - { - Color4 colour = colours.Yellow; - - if (IsHovered && IsDraggable - && !inputManager.HoveredDrawables.Any(o => o is PathControlPointPiece)) - colour = colour.Lighten(1); - - CirclePiece.Colour = colour; - } - - protected override bool OnDragStart(DragStartEvent e) - { - if (e.Button == MouseButton.Right || !IsDraggable) - return false; - - isDragging = true; - editorBeatmap?.BeginChange(); - - return true; - } - - protected override void OnDrag(DragEvent e) - { - double oldDistance = Slider.Path.Distance; - double proposedDistance = findClosestPathDistance(e); - - proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance); - proposedDistance = MathHelper.Clamp(proposedDistance, - 0.1 * oldDistance / Slider.SliderVelocityMultiplier, - 10 * oldDistance / Slider.SliderVelocityMultiplier); - - if (Precision.AlmostEquals(proposedDistance, oldDistance)) - return; - - Slider.SliderVelocityMultiplier *= proposedDistance / oldDistance; - Slider.Path.ExpectedDistance.Value = proposedDistance; - editorBeatmap?.Update(Slider); - } - - protected override void OnDragEnd(DragEndEvent e) - { - if (!isDragging) return; - - trimExcessControlPoints(Slider.Path); - - isDragging = false; - IsDraggable = false; - editorBeatmap?.EndChange(); - } - - /// - /// Trims control points from the end of the slider path which are not required to reach the expected end of the slider. - /// - /// The slider path to trim control points of. - private void trimExcessControlPoints(SliderPath sliderPath) - { - if (!sliderPath.ExpectedDistance.Value.HasValue) - return; - - double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray(); - int segmentIndex = 0; - - for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++) - { - if (!sliderPath.ControlPoints[i].Type.HasValue) continue; - - if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3)) - { - sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1); - sliderPath.ControlPoints[^1].Type = null; - break; - } - - segmentIndex++; - } - } - - /// - /// Finds the expected distance value for which the slider end is closest to the mouse position. - /// - private double findClosestPathDistance(DragEvent e) - { - const double step1 = 10; - const double step2 = 0.1; - - var desiredPosition = e.MousePosition - Slider.Position; - - if (!fullPathCache.IsValid) - fullPathCache.Value = new SliderPath(Slider.Path.ControlPoints.ToArray()); - - // Do a linear search to find the closest point on the path to the mouse position. - double bestValue = 0; - double minDistance = double.MaxValue; - - for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1) - { - double t = d / fullPathCache.Value.CalculatedDistance; - float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition); - - if (dist >= minDistance) continue; - - minDistance = dist; - bestValue = d; - } - - // Do another linear search to fine-tune the result. - for (double d = bestValue - step1; d <= bestValue + step1; d += step2) - { - double t = d / fullPathCache.Value.CalculatedDistance; - float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition); - - if (dist >= minDistance) continue; - - minDistance = dist; - bestValue = d; - } - - return bestValue; - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 4a949f5b48ee..f59ef298a79a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -34,7 +35,7 @@ public partial class SliderSelectionBlueprint : OsuSelectionBlueprint protected SliderBodyPiece BodyPiece { get; private set; } protected SliderCircleOverlay HeadOverlay { get; private set; } - protected SliderTailPiece TailPiece { get; private set; } + protected SliderCircleOverlay TailPiece { get; private set; } [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } @@ -60,6 +61,10 @@ public partial class SliderSelectionBlueprint : OsuSelectionBlueprint private readonly IBindable pathVersion = new Bindable(); private readonly BindableList selectedObjects = new BindableList(); + // Cached slider path which ignored the expected distance value. + private readonly Cached fullPathCache = new Cached(); + private bool isAdjustingLength; + public SliderSelectionBlueprint(Slider slider) : base(slider) { @@ -72,7 +77,7 @@ private void load() { BodyPiece = new SliderBodyPiece(), HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start), - TailPiece = CreateTailPiece(HitObject, SliderPosition.End), + TailPiece = CreateCircleOverlay(HitObject, SliderPosition.End), }; } @@ -81,6 +86,7 @@ protected override void LoadComplete() base.LoadComplete(); controlPoints.BindTo(HitObject.Path.ControlPoints); + controlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate(); pathVersion.BindTo(HitObject.Path.Version); pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject)); @@ -135,6 +141,9 @@ protected override void OnDeselected() { base.OnDeselected(); + if (isAdjustingLength) + endAdjustLength(); + updateVisualDefinition(); BodyPiece.RecyclePath(); } @@ -164,6 +173,12 @@ private void updateVisualDefinition() protected override bool OnMouseDown(MouseDownEvent e) { + if (isAdjustingLength) + { + endAdjustLength(); + return true; + } + switch (e.Button) { case MouseButton.Right: @@ -171,6 +186,7 @@ protected override bool OnMouseDown(MouseDownEvent e) return false; // Allow right click to be handled by context menu case MouseButton.Left: + // If there's more than two objects selected, ctrl+click should deselect if (e.ControlPressed && IsSelected && selectedObjects.Count < 2) { @@ -186,6 +202,106 @@ protected override bool OnMouseDown(MouseDownEvent e) return false; } + private void endAdjustLength() + { + trimExcessControlPoints(HitObject.Path); + isAdjustingLength = false; + changeHandler?.EndChange(); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (!isAdjustingLength) + return base.OnMouseMove(e); + + double oldDistance = HitObject.Path.Distance; + double proposedDistance = findClosestPathDistance(e); + + proposedDistance = MathHelper.Clamp(proposedDistance, 0, HitObject.Path.CalculatedDistance); + proposedDistance = MathHelper.Clamp(proposedDistance, + 0.1 * oldDistance / HitObject.SliderVelocityMultiplier, + 10 * oldDistance / HitObject.SliderVelocityMultiplier); + + if (Precision.AlmostEquals(proposedDistance, oldDistance)) + return false; + + HitObject.SliderVelocityMultiplier *= proposedDistance / oldDistance; + HitObject.Path.ExpectedDistance.Value = proposedDistance; + editorBeatmap?.Update(HitObject); + + return false; + } + + /// + /// Trims control points from the end of the slider path which are not required to reach the expected end of the slider. + /// + /// The slider path to trim control points of. + private void trimExcessControlPoints(SliderPath sliderPath) + { + if (!sliderPath.ExpectedDistance.Value.HasValue) + return; + + double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray(); + int segmentIndex = 0; + + for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++) + { + if (!sliderPath.ControlPoints[i].Type.HasValue) continue; + + if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3)) + { + sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1); + sliderPath.ControlPoints[^1].Type = null; + break; + } + + segmentIndex++; + } + } + + /// + /// Finds the expected distance value for which the slider end is closest to the mouse position. + /// + private double findClosestPathDistance(MouseMoveEvent e) + { + const double step1 = 10; + const double step2 = 0.1; + + var desiredPosition = e.MousePosition - HitObject.Position; + + if (!fullPathCache.IsValid) + fullPathCache.Value = new SliderPath(HitObject.Path.ControlPoints.ToArray()); + + // Do a linear search to find the closest point on the path to the mouse position. + double bestValue = 0; + double minDistance = double.MaxValue; + + for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1) + { + double t = d / fullPathCache.Value.CalculatedDistance; + float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition); + + if (dist >= minDistance) continue; + + minDistance = dist; + bestValue = d; + } + + // Do another linear search to fine-tune the result. + for (double d = bestValue - step1; d <= bestValue + step1; d += step2) + { + double t = d / fullPathCache.Value.CalculatedDistance; + float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition); + + if (dist >= minDistance) continue; + + minDistance = dist; + bestValue = d; + } + + return bestValue; + } + [CanBeNull] private PathControlPoint placementControlPoint; @@ -409,9 +525,10 @@ private void convertToStream() addControlPoint(rightClickPosition); changeHandler?.EndChange(); }), - new OsuMenuItem("Adjust distance", MenuItemType.Standard, () => + new OsuMenuItem("Adjust length", MenuItemType.Standard, () => { - TailPiece.IsDraggable = true; + isAdjustingLength = true; + changeHandler?.BeginChange(); }), new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream), }; @@ -427,6 +544,9 @@ private void convertToStream() public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { + if (isAdjustingLength) + return true; + if (BodyPiece.ReceivePositionalInputAt(screenSpacePos)) return true; @@ -443,6 +563,5 @@ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) } protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position); - protected virtual SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new SliderTailPiece(slider, position); } } From 956bdbca50afdc24e97bb6834adca8cc839c993e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 20 Jun 2024 00:17:16 +0200 Subject: [PATCH 041/579] fix tests --- .../TestSceneSliderControlPointPiece.cs | 13 +--- .../TestSceneSliderSelectionBlueprint.cs | 60 +++++-------------- .../Sliders/SliderSelectionBlueprint.cs | 4 +- 3 files changed, 17 insertions(+), 60 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 1a7430704dd1..99ced30ffe92 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -353,7 +353,7 @@ private partial class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; - public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece; + public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) @@ -362,7 +362,6 @@ public TestSliderBlueprint(Slider slider) } protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position); - protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position); } private partial class TestSliderCircleOverlay : SliderCircleOverlay @@ -374,15 +373,5 @@ public TestSliderCircleOverlay(Slider slider, SliderPosition position) { } } - - private partial class TestSliderTailPiece : SliderTailPiece - { - public new HitCirclePiece CirclePiece => base.CirclePiece; - - public TestSliderTailPiece(Slider slider, SliderPosition position) - : base(slider, position) - { - } - } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 3faf18146518..812b34dfe22e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Linq; using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -164,51 +165,29 @@ public void TestMultipleControlPointDeselectionViaClickOutside() } [Test] - public void TestDragSliderTail() + public void TestAdjustDistance() { - AddStep("move mouse to slider tail", () => - { - Vector2 position = slider.EndPosition + new Vector2(10, 0); - InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); - }); - AddStep("shift + drag", () => - { - InputManager.PressKey(Key.ShiftLeft); - InputManager.PressButton(MouseButton.Left); - }); + AddStep("start adjust length", + () => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value()); moveMouseToControlPoint(1); - AddStep("release", () => - { - InputManager.ReleaseButton(MouseButton.Left); - InputManager.ReleaseKey(Key.ShiftLeft); - }); - + AddStep("end adjust length", () => InputManager.Click(MouseButton.Right)); AddAssert("expected distance halved", () => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1)); - AddStep("move mouse to slider tail", () => - { - Vector2 position = slider.EndPosition + new Vector2(10, 0); - InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); - }); - AddStep("shift + drag", () => - { - InputManager.PressKey(Key.ShiftLeft); - InputManager.PressButton(MouseButton.Left); - }); + AddStep("start adjust length", + () => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value()); AddStep("move mouse beyond last control point", () => { Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0); InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); }); - AddStep("release", () => - { - InputManager.ReleaseButton(MouseButton.Left); - InputManager.ReleaseKey(Key.ShiftLeft); - }); - + AddStep("end adjust length", () => InputManager.Click(MouseButton.Right)); AddAssert("expected distance is calculated distance", () => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1)); + + moveMouseToControlPoint(1); + AddAssert("expected distance is unchanged", + () => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1)); } private void moveHitObject() @@ -227,7 +206,7 @@ private void checkPositions() () => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); AddAssert("tail positioned correctly", - () => Precision.AlmostEquals(blueprint.TailPiece.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); + () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); } private void moveMouseToControlPoint(int index) @@ -246,7 +225,7 @@ private partial class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; - public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece; + public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) @@ -255,7 +234,6 @@ public TestSliderBlueprint(Slider slider) } protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position); - protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position); } private partial class TestSliderCircleOverlay : SliderCircleOverlay @@ -267,15 +245,5 @@ public TestSliderCircleOverlay(Slider slider, SliderPosition position) { } } - - private partial class TestSliderTailPiece : SliderTailPiece - { - public new HitCirclePiece CirclePiece => base.CirclePiece; - - public TestSliderTailPiece(Slider slider, SliderPosition position) - : base(slider, position) - { - } - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index f59ef298a79a..eb269ba68032 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -35,7 +35,7 @@ public partial class SliderSelectionBlueprint : OsuSelectionBlueprint protected SliderBodyPiece BodyPiece { get; private set; } protected SliderCircleOverlay HeadOverlay { get; private set; } - protected SliderCircleOverlay TailPiece { get; private set; } + protected SliderCircleOverlay TailOverlay { get; private set; } [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } @@ -77,7 +77,7 @@ private void load() { BodyPiece = new SliderBodyPiece(), HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start), - TailPiece = CreateCircleOverlay(HitObject, SliderPosition.End), + TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End), }; } From 225b309ba357b177a6259a447afea8e18e261ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Jun 2024 16:27:07 +0200 Subject: [PATCH 042/579] Reimplement stable polygon tool Addresses https://github.com/ppy/osu/discussions/19970. While yes, https://github.com/ppy/osu/pull/26303 is also a thing, in discussing with users I don't think that grids are going to be able to deprecate this feature. Logic transcribed verbatim from stable. --- .../Edit/GenerateToolboxGroup.cs | 54 +++++ .../Edit/OsuHitObjectComposer.cs | 3 +- .../Edit/PolygonGenerationPopover.cs | 193 ++++++++++++++++++ 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs diff --git a/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs new file mode 100644 index 000000000000..4e188a2b8691 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class GenerateToolboxGroup : EditorToolboxGroup + { + private readonly EditorToolButton polygonButton; + + public GenerateToolboxGroup() + : base("Generate") + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(5), + Children = new Drawable[] + { + polygonButton = new EditorToolButton("Polygon", + () => new SpriteIcon { Icon = FontAwesome.Solid.Spinner }, + () => new PolygonGenerationPopover()), + } + }; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat) return false; + + switch (e.Key) + { + case Key.D: + if (!e.ControlPressed || !e.ShiftPressed) + return false; + + polygonButton.TriggerClick(); + return true; + + default: + return false; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 41f6b41f8261..fab529855450 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,7 +64,7 @@ protected override IEnumerable CreateTernaryButtons() private Bindable placementObject; [Cached(typeof(IDistanceSnapProvider))] - protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); + public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup(); @@ -109,6 +109,7 @@ private void load() RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler, }, + new GenerateToolboxGroup(), FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs new file mode 100644 index 000000000000..6325de5851d5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -0,0 +1,193 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class PolygonGenerationPopover : OsuPopover + { + private SliderWithTextBoxInput distanceSnapInput = null!; + private SliderWithTextBoxInput offsetAngleInput = null!; + private SliderWithTextBoxInput repeatCountInput = null!; + private SliderWithTextBoxInput pointInput = null!; + private RoundedButton commitButton = null!; + + private readonly List insertedCircles = new List(); + private bool began; + private bool committed; + + [Resolved] + private IBeatSnapProvider beatSnapProvider { get; set; } = null!; + + [Resolved] + private EditorClock editorClock { get; set; } = null!; + + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved] + private HitObjectComposer composer { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + Width = 220, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Children = new Drawable[] + { + distanceSnapInput = new SliderWithTextBoxInput("Distance snap:") + { + Current = new BindableNumber(1) + { + MinValue = 0.1, + MaxValue = 6, + Precision = 0.1, + Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value, + }, + Instantaneous = true + }, + offsetAngleInput = new SliderWithTextBoxInput("Offset angle:") + { + Current = new BindableNumber + { + MinValue = 0, + MaxValue = 180, + Precision = 1 + }, + Instantaneous = true + }, + repeatCountInput = new SliderWithTextBoxInput("Repeats:") + { + Current = new BindableNumber(1) + { + MinValue = 1, + MaxValue = 10, + Precision = 1 + }, + Instantaneous = true + }, + pointInput = new SliderWithTextBoxInput("Vertices:") + { + Current = new BindableNumber(3) + { + MinValue = 3, + MaxValue = 10, + Precision = 1, + }, + Instantaneous = true + }, + commitButton = new RoundedButton + { + RelativeSizeAxes = Axes.X, + Text = "Create", + Action = commit + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + changeHandler?.BeginChange(); + began = true; + + distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon()); + offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon()); + repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon()); + pointInput.Current.BindValueChanged(_ => tryCreatePolygon()); + tryCreatePolygon(); + } + + private void tryCreatePolygon() + { + double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime); + TimingControlPoint timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(startTime); + double timeSpacing = timingPoint.BeatLength / editorBeatmap.BeatDivisor; + IHasSliderVelocity lastWithSliderVelocity = editorBeatmap.HitObjects.Where(ho => ho.GetEndTime() <= startTime).OfType().LastOrDefault() ?? new Slider(); + double velocity = OsuHitObject.BASE_SCORING_DISTANCE * editorBeatmap.Difficulty.SliderMultiplier + / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(lastWithSliderVelocity, timingPoint, OsuRuleset.SHORT_NAME); + double length = distanceSnapInput.Current.Value * velocity * timeSpacing; + float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value))); + + editorBeatmap.RemoveRange(insertedCircles); + insertedCircles.Clear(); + + var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; + bool first = true; + + for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i) + { + float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value); + var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle)); + + var circle = new HitCircle + { + Position = position, + StartTime = startTime, + NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True, + }; + // TODO: probably ensure samples also follow current ternary status (not trivial) + circle.Samples.Add(circle.CreateHitSampleInfo()); + + if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) + { + commitButton.Enabled.Value = false; + return; + } + + insertedCircles.Add(circle); + startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); + + first = false; + } + + editorBeatmap.AddRange(insertedCircles); + commitButton.Enabled.Value = true; + } + + private void commit() + { + changeHandler?.EndChange(); + committed = true; + Hide(); + } + + protected override void PopOut() + { + base.PopOut(); + + if (began && !committed) + { + editorBeatmap.RemoveRange(insertedCircles); + changeHandler?.EndChange(); + } + } + } +} From fbc99894279ab1da456cef2ceebc0615eefe24b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 01:01:26 +0300 Subject: [PATCH 043/579] Simplify default layout initialisation --- osu.Game/Skinning/ArgonSkinTransformer.cs | 41 ++++++++-------------- osu.Game/Skinning/LegacySkinTransformer.cs | 37 +++++-------------- 2 files changed, 23 insertions(+), 55 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs index 387a7a9c0b18..8ca8f79b4169 100644 --- a/osu.Game/Skinning/ArgonSkinTransformer.cs +++ b/osu.Game/Skinning/ArgonSkinTransformer.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK; @@ -17,34 +17,21 @@ public ArgonSkinTransformer(ISkin skin) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - switch (lookup) + if (lookup is SkinComponentsContainerLookup containerLookup + && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents + && containerLookup.Ruleset != null) { - case SkinComponentsContainerLookup containerLookup: - switch (containerLookup.Target) + return base.GetDrawableComponent(lookup) ?? new Container + { + RelativeSizeAxes = Axes.Both, + Child = new ArgonComboCounter { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); - - rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => - { - var combo = container.OfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.BottomLeft; - combo.Origin = Anchor.BottomLeft; - combo.Position = new Vector2(36, -66); - combo.Scale = new Vector2(1.3f); - } - }) - { - new ArgonComboCounter(), - }; - - return rulesetHUDComponents; - } - - break; + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Position = new Vector2(36, -66), + Scale = new Vector2(1.3f), + }, + }; } return base.GetDrawableComponent(lookup); diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 3ea316c0c794..dbfa52de84e0 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.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.Linq; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; -using osuTK; using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning @@ -25,33 +24,15 @@ public LegacySkinTransformer(ISkin skin) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - switch (lookup) + if (lookup is SkinComponentsContainerLookup containerLookup + && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents + && containerLookup.Ruleset != null) { - case SkinComponentsContainerLookup containerLookup: - switch (containerLookup.Target) - { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - var rulesetHUDComponents = base.GetDrawableComponent(lookup); - - rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => - { - var combo = container.OfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.BottomLeft; - combo.Origin = Anchor.BottomLeft; - combo.Scale = new Vector2(1.28f); - } - }) - { - new LegacyComboCounter() - }; - - return rulesetHUDComponents; - } - - break; + return base.GetDrawableComponent(lookup) ?? new Container + { + RelativeSizeAxes = Axes.Both, + Child = new LegacyComboCounter(), + }; } return base.GetDrawableComponent(lookup); From e8de293be5f813731f97ab8132c569c914dca59a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 01:01:32 +0300 Subject: [PATCH 044/579] Remove pointless assert --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 462fd5ab6448..17218b459aca 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; @@ -33,7 +32,6 @@ public CatchLegacySkinTransformer(ISkin skin) switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - Debug.Assert(containerLookup.Ruleset.ShortName == CatchRuleset.SHORT_NAME); // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. return Skin.GetDrawableComponent(lookup); } From 78e0126f16e90aca5459b288e42443bbf2b3387e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 04:24:58 +0300 Subject: [PATCH 045/579] Migrate combo counter layouts in custom skins to correct target-ruleset pairs --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Skinning/ArgonSkin.cs | 19 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 19 +++++++++++++++++++ osu.Game/Skinning/SkinImporter.cs | 2 ++ osu.Game/Skinning/SkinInfo.cs | 15 +++++++++++++++ 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1ece81be5095..606bc5e10cab 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -93,8 +93,9 @@ public class RealmAccess : IDisposable /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. + /// 42 2024-06-25 Add SkinInfo.LayoutVersion to allow performing migrations of components on structural changes. /// - private const int schema_version = 41; + private const int schema_version = 42; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 707281db3107..743ce388106e 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; @@ -69,6 +70,24 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) // Purple new Color4(92, 0, 241, 255), }; + + if (skin.LayoutVersion < 20240625 + && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout) + && hudLayout.TryGetDrawableInfo(null, out var hudComponents)) + { + var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(ArgonComboCounter)).ToArray(); + + if (comboCounters.Any()) + { + hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray()); + + resources.RealmAccess.Run(r => + { + foreach (var ruleset in r.All()) + hudLayout.Update(ruleset, comboCounters); + }); + } + } } public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b71b626b4ed0..c3e619431ef9 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,6 +19,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -56,6 +57,24 @@ public LegacySkin(SkinInfo skin, IStorageResourceProvider resources) protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore, string configurationFilename = @"skin.ini") : base(skin, resources, fallbackStore, configurationFilename) { + if (resources != null + && skin.LayoutVersion < 20240625 + && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout) + && hudLayout.TryGetDrawableInfo(null, out var hudComponents)) + { + var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(LegacyComboCounter)).ToArray(); + + if (comboCounters.Any()) + { + hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray()); + + resources.RealmAccess.Run(r => + { + foreach (var ruleset in r.All()) + hudLayout.Update(ruleset, comboCounters); + }); + } + } } protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 59c7f0ba2679..714427f40da6 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -223,6 +223,8 @@ public bool Save(Skin skin) } } + s.LayoutVersion = SkinInfo.LATEST_LAYOUT_VERSION; + string newHash = ComputeHash(s); hadChanges = newHash != s.Hash; diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 9763d3b57e9a..a3d5771b5e66 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -39,6 +39,21 @@ public class SkinInfo : RealmObject, IHasRealmFiles, IEquatable, IHasG public bool Protected { get; set; } + /// + /// The latest version in YYYYMMDD format for skin layout migrations. + /// + /// + /// + /// 20240625: Moves combo counters from ruleset-agnostic to ruleset-specific HUD targets. + /// + /// + public const int LATEST_LAYOUT_VERSION = 20240625; + + /// + /// A version in YYYYMMDD format for applying skin layout migrations. + /// + public int LayoutVersion { get; set; } + public virtual Skin CreateInstance(IStorageResourceProvider resources) { var type = string.IsNullOrEmpty(InstantiationInfo) From fc2202e0cc9cdf1191675272607aa7e51f2037e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 05:54:56 +0300 Subject: [PATCH 046/579] Fix migration logic overwriting existing components in ruleset targets --- osu.Game/Skinning/ArgonSkin.cs | 6 +++++- osu.Game/Skinning/LegacySkin.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 743ce388106e..4cd54c06f073 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -84,7 +84,11 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) resources.RealmAccess.Run(r => { foreach (var ruleset in r.All()) - hudLayout.Update(ruleset, comboCounters); + { + hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents) + ? rulesetComponents.Concat(comboCounters).ToArray() + : comboCounters); + } }); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index c3e619431ef9..f148bad96e75 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -71,7 +71,11 @@ protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResour resources.RealmAccess.Run(r => { foreach (var ruleset in r.All()) - hudLayout.Update(ruleset, comboCounters); + { + hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents) + ? rulesetComponents.Concat(comboCounters).ToArray() + : comboCounters); + } }); } } From dc1fb4fdca462a8d8123e695177aa61f951a78da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 05:54:59 +0300 Subject: [PATCH 047/579] Add test coverage --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 3c97700fb082..2470c320cc71 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -13,12 +13,14 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Testing; +using osu.Game.Database; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; using osu.Game.Skinning.Components; @@ -39,6 +41,9 @@ public partial class TestSceneSkinEditor : PlayerTestScene [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); + [Resolved] + private SkinManager skins { get; set; } = null!; + private SkinComponentsContainer targetContainer => Player.ChildrenOfType().First(); [SetUpSteps] @@ -46,6 +51,7 @@ public override void SetUpSteps() { base.SetUpSteps(); + AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault()); AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded); AddStep("reload skin editor", () => @@ -369,6 +375,84 @@ public void TestCopyPaste() () => Is.EqualTo(3)); } + private SkinComponentsContainer globalHUDTarget => Player.ChildrenOfType() + .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null); + + private SkinComponentsContainer rulesetHUDTarget => Player.ChildrenOfType() + .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null); + + [Test] + public void TestMigrationArgon() + { + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded); + AddStep("add combo to global hud target", () => + { + globalHUDTarget.Add(new ArgonComboCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + }); + + Live modifiedSkin = null!; + + AddStep("select another skin", () => + { + modifiedSkin = skins.CurrentSkinInfo.Value; + skins.CurrentSkinInfo.SetDefault(); + }); + AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0)); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin); + AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is ArgonComboCounter)); + AddAssert("ruleset hud target contains both combos", () => + { + var target = rulesetHUDTarget; + + return target.Components.Count == 2 && + target.Components[0] is ArgonComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft && + target.Components[1] is ArgonComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre; + }); + AddStep("save skin", () => skinEditor.Save()); + AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION); + } + + [Test] + public void TestMigrationLegacy() + { + AddStep("select legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo); + + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded); + AddStep("add combo to global hud target", () => + { + globalHUDTarget.Add(new LegacyComboCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + }); + + Live modifiedSkin = null!; + + AddStep("select another skin", () => + { + modifiedSkin = skins.CurrentSkinInfo.Value; + skins.CurrentSkinInfo.SetDefault(); + }); + AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0)); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin); + AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is LegacyComboCounter)); + AddAssert("ruleset hud target contains both combos", () => + { + var target = rulesetHUDTarget; + + return target.Components.Count == 2 && + target.Components[0] is LegacyComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft && + target.Components[1] is LegacyComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre; + }); + AddStep("save skin", () => skinEditor.Save()); + AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION); + } + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler From 0ee89183cc28b3cd39e14c30a4ec83a353a9db9d Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Wed, 26 Jun 2024 15:25:41 -0400 Subject: [PATCH 048/579] initial implementation --- osu.Desktop/OsuGameDesktop.cs | 34 +++----- osu.Desktop/Program.cs | 39 +++------ ...lUpdateManager.cs => VeloUpdateManager.cs} | 86 ++++--------------- osu.Desktop/osu.Desktop.csproj | 2 +- 4 files changed, 38 insertions(+), 123 deletions(-) rename osu.Desktop/Updater/{SquirrelUpdateManager.cs => VeloUpdateManager.cs} (52%) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index e8783c997aed..245d00dc5336 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -19,7 +19,6 @@ using osu.Framework.Allocation; using osu.Game.IO; using osu.Game.IPC; -using osu.Game.Online.Multiplayer; using osu.Game.Performance; using osu.Game.Utils; using SDL; @@ -103,35 +102,22 @@ protected override UpdateManager CreateUpdateManager() if (!string.IsNullOrEmpty(packageManaged)) return new NoActionUpdateManager(); - switch (RuntimeInfo.OS) - { - case RuntimeInfo.Platform.Windows: - Debug.Assert(OperatingSystem.IsWindows()); - - return new SquirrelUpdateManager(); - - default: - return new SimpleUpdateManager(); - } + return new VeloUpdateManager(); } public override bool RestartAppWhenExited() { - switch (RuntimeInfo.OS) + try { - case RuntimeInfo.Platform.Windows: - Debug.Assert(OperatingSystem.IsWindows()); - - // Of note, this is an async method in squirrel that adds an arbitrary delay before returning - // likely to ensure the external process is in a good state. - // - // We're not waiting on that here, but the outro playing before the actual exit should be enough - // to cover this. - Squirrel.UpdateManager.RestartAppWhenExited().FireAndForget(); - return true; + Process.Start(Process.GetCurrentProcess().MainModule?.FileName ?? throw new InvalidOperationException()); + Environment.Exit(0); + return true; + } + catch (Exception e) + { + Logger.Error(e, "Failed to restart application"); + return base.RestartAppWhenExited(); } - - return base.RestartAppWhenExited(); } protected override void LoadComplete() diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 23e56cdce9c6..7c23c15d5a06 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Runtime.Versioning; using osu.Desktop.LegacyIpc; using osu.Desktop.Windows; using osu.Framework; @@ -14,7 +13,7 @@ using osu.Game.IPC; using osu.Game.Tournament; using SDL; -using Squirrel; +using Velopack; namespace osu.Desktop { @@ -66,10 +65,10 @@ public static void Main(string[] args) return; } } - - setupSquirrel(); } + setupVelo(); + // NVIDIA profiles are based on the executable name of a process. // Lazer and stable share the same executable name. // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. @@ -177,32 +176,14 @@ private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args) return false; } - [SupportedOSPlatform("windows")] - private static void setupSquirrel() + private static void setupVelo() { - SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) => - { - tools.CreateShortcutForThisExe(); - tools.CreateUninstallerRegistryEntry(); - WindowsAssociationManager.InstallAssociations(); - }, onAppUpdate: (_, tools) => - { - tools.CreateUninstallerRegistryEntry(); - WindowsAssociationManager.UpdateAssociations(); - }, onAppUninstall: (_, tools) => - { - tools.RemoveShortcutForThisExe(); - tools.RemoveUninstallerRegistryEntry(); - WindowsAssociationManager.UninstallAssociations(); - }, onEveryRun: (_, _, _) => - { - // While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently - // causes the right-click context menu to function incorrectly. - // - // This may turn out to be non-required after an alternative solution is implemented. - // see https://github.com/clowd/Clowd.Squirrel/issues/24 - // tools.SetProcessAppUserModelId(); - }); + VelopackApp + .Build() + .WithFirstRun(v => + { + if (OperatingSystem.IsWindows()) WindowsAssociationManager.InstallAssociations(); + }).Run(); } } } diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/VeloUpdateManager.cs similarity index 52% rename from osu.Desktop/Updater/SquirrelUpdateManager.cs rename to osu.Desktop/Updater/VeloUpdateManager.cs index dba157a6e951..8fc68f77cd03 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/VeloUpdateManager.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Versioning; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Logging; @@ -10,30 +9,15 @@ using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Screens.Play; -using Squirrel.SimpleSplat; -using Squirrel.Sources; -using LogLevel = Squirrel.SimpleSplat.LogLevel; -using UpdateManager = osu.Game.Updater.UpdateManager; +using osu.Game.Updater; namespace osu.Desktop.Updater { - [SupportedOSPlatform("windows")] - public partial class SquirrelUpdateManager : UpdateManager + public partial class VeloUpdateManager : UpdateManager { - private Squirrel.UpdateManager? updateManager; + private Velopack.UpdateManager? updateManager; private INotificationOverlay notificationOverlay = null!; - public Task PrepareUpdateAsync() => Squirrel.UpdateManager.RestartAppWhenExited(); - - private static readonly Logger logger = Logger.GetLogger("updater"); - - /// - /// Whether an update has been downloaded but not yet applied. - /// - private bool updatePending; - - private readonly SquirrelLogger squirrelLogger = new SquirrelLogger(); - [Resolved] private OsuGameBase game { get; set; } = null!; @@ -44,13 +28,11 @@ public partial class SquirrelUpdateManager : UpdateManager private void load(INotificationOverlay notifications) { notificationOverlay = notifications; - - SquirrelLocator.CurrentMutable.Register(() => squirrelLogger, typeof(ILogger)); } protected override async Task PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false); - private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification? notification = null) + private async Task checkForUpdateAsync(UpdateProgressNotification? notification = null) { // should we schedule a retry on completion of this check? bool scheduleRecheck = true; @@ -63,27 +45,27 @@ private async Task checkForUpdateAsync(bool useDeltaPatching = true, Updat if (localUserInfo?.IsPlaying.Value == true) return false; - updateManager ??= new Squirrel.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), @"osulazer"); + updateManager ??= new Velopack.UpdateManager(new Velopack.Sources.GithubSource(@"https://github.com/ppy/osu", github_token, false)); - var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false); + var info = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false); - if (info.ReleasesToApply.Count == 0) + if (info == null) { - if (updatePending) + // If there is an update pending restart, show the notification again. + if (updateManager.IsUpdatePendingRestart) { - // the user may have dismissed the completion notice, so show it again. notificationOverlay.Post(new UpdateApplicationCompleteNotification { Activated = () => { restartToApplyUpdate(); return true; - }, + } }); return true; } - // no updates available. bail and retry later. + // Otherwise there's no updates available. Bail and retry later. return false; } @@ -103,31 +85,17 @@ private async Task checkForUpdateAsync(bool useDeltaPatching = true, Updat try { - await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false); + await updateManager.DownloadUpdatesAsync(info, p => notification.Progress = p / 100f).ConfigureAwait(false); notification.StartInstall(); - await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false); - notification.State = ProgressNotificationState.Completed; - updatePending = true; } catch (Exception e) { - if (useDeltaPatching) - { - logger.Add(@"delta patching failed; will attempt full download!"); - - // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) - // try again without deltas. - await checkForUpdateAsync(false, notification).ConfigureAwait(false); - } - else - { // In the case of an error, a separate notification will be displayed. notification.FailDownload(); Logger.Error(e, @"update failed!"); - } } } catch (Exception) @@ -149,32 +117,12 @@ private async Task checkForUpdateAsync(bool useDeltaPatching = true, Updat private bool restartToApplyUpdate() { - PrepareUpdateAsync() - .ContinueWith(_ => Schedule(() => game.AttemptExit())); - return true; - } + if (updateManager == null) + return false; - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - updateManager?.Dispose(); - } - - private class SquirrelLogger : ILogger, IDisposable - { - public LogLevel Level { get; set; } = LogLevel.Info; - - public void Write(string message, LogLevel logLevel) - { - if (logLevel < Level) - return; - - logger.Add(message); - } - - public void Dispose() - { - } + updateManager.WaitExitThenApplyUpdates(null); + Schedule(() => game.AttemptExit()); + return true; } } } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index e7a63bd921a9..7df82e12813b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -23,10 +23,10 @@ - + From 1025e5b3cc756b1cbd01216be157b3e0b270225f Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Wed, 26 Jun 2024 21:55:22 -0400 Subject: [PATCH 049/579] rewrite the restart function --- osu.Desktop/OsuGameDesktop.cs | 11 ++++++++--- .../Screens/Setup/TournamentSwitcher.cs | 3 +-- osu.Game/OsuGameBase.cs | 2 +- .../Settings/Sections/Graphics/RendererSettings.cs | 3 +-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 245d00dc5336..3019aefdc019 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -105,18 +105,23 @@ protected override UpdateManager CreateUpdateManager() return new VeloUpdateManager(); } - public override bool RestartAppWhenExited() + public override bool RestartApp() { try { - Process.Start(Process.GetCurrentProcess().MainModule?.FileName ?? throw new InvalidOperationException()); + var startInfo = new ProcessStartInfo + { + FileName = Process.GetCurrentProcess().MainModule!.FileName, + UseShellExecute = true + }; + Process.Start(startInfo); Environment.Exit(0); return true; } catch (Exception e) { Logger.Error(e, "Failed to restart application"); - return base.RestartAppWhenExited(); + return base.RestartApp(); } } diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs index e55cbc2dbb4d..69ead451ba12 100644 --- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs +++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs @@ -31,8 +31,7 @@ private void load(TournamentStorage storage) Action = () => { - game.RestartAppWhenExited(); - game.AttemptExit(); + game.RestartApp(); }; folderButton.Action = () => storage.PresentExternally(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5533ee833776..db603f0046fe 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -513,7 +513,7 @@ public virtual void AttemptExit() /// If supported by the platform, the game will automatically restart after the next exit. /// /// Whether a restart operation was queued. - public virtual bool RestartAppWhenExited() => false; + public virtual bool RestartApp() => false; public bool Migrate(string path) { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index a8b127d5229a..17e345c2c8dd 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -67,9 +67,8 @@ private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, IDi if (r.NewValue == RendererType.Automatic && automaticRendererInUse) return; - if (game?.RestartAppWhenExited() == true) + if (game?.RestartApp() == true) { - game.AttemptExit(); } else { From 36a3765ee4e2ef3240a265549ebc6a734f696c62 Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 27 Jun 2024 12:57:24 -0400 Subject: [PATCH 050/579] Replace with attemptexit to better display how restarting is borked --- osu.Desktop/OsuGameDesktop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b6053edf778e..28f3d3dc5d1b 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -114,7 +114,7 @@ public override bool RestartApp() UseShellExecute = true }; Process.Start(startInfo); - Environment.Exit(0); + base.AttemptExit(); return true; } catch (Exception e) From 0c34e7bebbce5a989af00d8daed25331ff33321a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Jul 2024 06:48:05 +0300 Subject: [PATCH 051/579] Store layout version in `SkinLayoutVersion` instead and refactor migration code --- .../Archives/argon-layout-version-0.osk | Bin 0 -> 1550 bytes .../Archives/classic-layout-version-0.osk | Bin 0 -> 1382 bytes .../Archives/triangles-layout-version-0.osk | Bin 0 -> 1378 bytes .../Visual/Gameplay/TestSceneSkinEditor.cs | 119 ++++++++++-------- osu.Game/Database/RealmAccess.cs | 3 +- osu.Game/Skinning/ArgonSkin.cs | 23 ---- osu.Game/Skinning/LegacySkin.cs | 23 ---- osu.Game/Skinning/Skin.cs | 116 ++++++++++++----- osu.Game/Skinning/SkinImporter.cs | 2 - osu.Game/Skinning/SkinInfo.cs | 15 --- osu.Game/Skinning/SkinLayoutInfo.cs | 18 ++- 11 files changed, 165 insertions(+), 154 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk create mode 100644 osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk create mode 100644 osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk diff --git a/osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk b/osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk new file mode 100644 index 0000000000000000000000000000000000000000..f767033eb1de68d16bbb5c4fa9cb5a0fb145425d GIT binary patch literal 1550 zcmWIWW@Zs#U|`^2*qU%JLZU!8aS@Pr04&15P@J8aryU#0>+j!P%R^ZV zg}4>xPMtgTR>x7b?S}2;yNh-Te99H8o3uZn>FxoZ&$acRWS&pExZ=B&MdgApPaRI? z(gk*z3!Y?ss$+>fEq0aT?Jw3G`R8|9rytwm=6Y8p^n%5Cr^borgHH=K3P*ZvG2E`q z?f$8}cvjaZ`>3Fr6*?0|Z!d~a-1uywCWCZVC!GrLPwn(h=yT<8}Ug zcVN&310w^YOCc9dob^BHbK1w3(WuvSYKeo@obDiD?@!$hvz$I}`uyqiXN5=eDvzpm zR;teIRGs;=a$aTT&w%8wcN)W9u%@R!dz$)e>1)fT9EOi2A_nlPT3PLiRVP}MKN^@>S#MeMs(e1@6d@1uz@?tiQ^imGf3x}gznAY$ylqUq+BY%o zGGqJtB$aIWwb6?uO&=9G?5y19Gk@*WdAH6z3pSWGD}?i>le&YSGmAji7t6Vm@BK8i zJmk^EE#Y<{UBKz?F8QeKb&OhSM;9hr>prrUE9{8fzKioZ=b?oSF3L|!SI_6M;NE}m zk58YYOjpb2sK1HawJT?sta%$UG;N5 zrI_ECHt)Clp7;$5`+D!@rqBJ};JVSQjo;7BUMNxL;)}VwAK5o`bn1SLY5a92FJZ?# zqeU?X(-;<*3QxS%*Vrk!fG>on#8$Wb^W7OA6%QpnJ-xbF_qR6FqNmUA%t+qPyz$a% zrbi}IIAUjV@J8qf`Q`O}Sml}|=pZK;{cC?`!Cj{bJJL9mO|}c3(Bz#tZ}~#&I5iE? z)2|yYGk!2y`I7Oem0em^){~-*CzmdJ<&tDS$>ZChWiwoi7DycV{(ATA_Nf{i0y``x zOR{wRZA@R3cBaK(w{ooIOfN?W6`fz_!h6BZlzOlbS;rR9IPve7c-{;A2DMpviH^>U`HDJeaCupm-ZOzxDz zYJQ7J77LYH_NAzO+@o+rtaj;73)aN6$=}WixmCZOG1YP6%4p_D-{?Deg15id=0DGp zj%&XWcl-M%<1fLNrp%ta?2gmVOskzoPgm~#^JRvey_xL_Jvqn7N z^YVFa6U=o?Rx7axcO_r0*R=KT-!W5ia?tODYWD3K1=34PGS-$ComMRWspj3aqTk4= znCt51TNz^Rt9`Ai58t};zO`lY?9{nQ5xzUSCip#mzW#tF*E6y3z0s=cS4gd!|Ly0u zyy|-&{)UIv#@cWH`+feIqxJbVfA>Z8Mcw(h_v5<<@AOe~lg!klDm7p((gb2YAP&e$ ztW3*H%}GJz_99I$9e;k^z|eRCstaBSp=(7i5)fL`8F3X5=w_e?1j3B1Ea<@!;LXYg Pl4k+Jk3gE86~qGo@tS=8 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk b/osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk new file mode 100644 index 0000000000000000000000000000000000000000..8240510f7c20e7d192efbfbba03ff9243f6f51dc GIT binary patch literal 1382 zcmWIWW@Zs#U|`^2*p+ZEVoSr(w@ZM$LtqgGhT`nZJiW}kOx2!^T!$P4SbzWSS{}+` zD8;Hcck0}!w>pl_y8Yte-F0_jIqGJ&&VQmWu~B+PqTTxP$fWw@EwA=|^IMk@V&v`S z&v_-4cS@}DJ$uJ%n~px^^u5cmt$Y8S*7jpIM_Vr`gkG>Z@6;IhZ`m|uru0oyGSmE` z#rmF{Pcs+!T5pnPv-HU+7-5q<$ITi2E5u*dkGS$5jI*OaeF{OwU0OSR=ei)XDp zD;|L2lhb)8+8cnrm2uo0m(j@AWNL{+)tv4iVee1f4!4{>Z~FY{^k;=f z^D2+3c2=s+>{OlkvvOW#=FfoUuXh@!y+0xfLmn>7A_H4?JCCk*+!kb)5 zMH0HE?8st7b?Q;ev-K%V3=Et27#PHWPW4U9%<~9!an8>z$j?j7D=7xM`RX*^{96tJ zZSVhZb#IRR$=$47=&?{YG+MNEiLJ}Tt!B-8Q*ZW`UH1$)Qvd!|;Q_Yi7fx}taDTEs z{D$ZJkFE2q+AhxJ_|u@w6@PP5xxIvc)a5Lv%9fS;_Gzx`4bo2vpKX0%qTY1LUrpB= zo@pPFNaXy*$Ju9|ysz)ajC7wygWl;nmw&K237vE6ds=a#{MQM`X_-C7tBe?x*=7pO zm~~92TcdOT!Dn6T8+s$$GOyp6Z})hTMr_*k?3MOe2i^Q0+}QSf*3}bk?p0AP^B#X~ z-+f|R(3eTtF=AUwf`1>dIVKWknN?BZrDt~Szpt3qRnDIVqLo}Ko>w>49egyUd(VR4 zzB|!}Jz2qzr(9@l*z@RY?(+pN>mS(m?`)X- zVckY8$&9tCN5r`Pw0&y4ua$H?*C{60zyF6)pK;)wTj@?WKTmiY9k$=}%HKx4^vL7q zHuBFk|CV)s#}QGzjy;QaNG#s?` zy3eMhEA?#Pc;BfaxX|V4XRV2jx`E67D8_uQu8r$>@A!aa)}^G{RSgBbN4Z=yt}gr0 z{Wgm4sNjX#HBX;kc=*aMq4Vpfm%iHfL;W5F+wRWHuFjKZ{bek%NBMBk-(^?VuGEbV z`&j#X&eX|uoO#x__oJpunW;%tYQXfU3BO%H&sw+%ZJlFot z8i3*xCI8FTl|Wxi0b*Vt2Kgj2FD+j$t2jSz^|PRWfKT3Md_y+{1)S2?3k~TA)Y3fV zbH@9;r*2&2qLBigjx*epPKX3Z{>GWrX zNAoI=s&-bY&g@j3`Ll9fW#-R-#b56a(S zjV`hsCh1H-HwrT_hymT`o0ysB5$fWcpIeZhmzq~n40i0>aNqn}4gz~WYwMibU?kzO z@`z&VwvfeE%?XV*=R5d6P4`%%MOt9GS*pAH`VarVudp8IE_fA33* zyvlef`c>f%wX{vYF77g);mE9Kd+O}ptFP{z`Y<(jlFosMRY6CeX!kbs)UpV4^;r0x z_)z4)X0X`sdaY#TCjI1vjM>)~SD*dGx?+aN-eq@lCi-pmopoegWbI}pw-=lpra^I= z-8LSd`CoQY=bYvjoijdXeuQ02}qK9uhag|MQnq~ z!92b_Aq)0xu%5B*`WcPz3Hl6M(v$)+d4JCNrT93w#pJ=&(x;y1Zhx@-rg|vw$2RX3 z1#iL+lrY*Vt~y_9@AgXFJ#qKHQ^#)?Zd0?JZ*bP4Xt|FrPkrXgr8nR2v2JtN$;Zxr zTP~inPn)IHboR82t=vCm-OY=1pR?+Lw_r~I8;954%Q0S`izmM?@lxNvdGg8dg?mbw zmvoyL_8Q8hz2TjFv$9~y#r5W=b}jmMurkp7&^hU3`zic0|Mh5mWS7__`qO1&!=6&p zAMeE(clUf{+O=2aPv>$*7lkdSXEKDhpPA3CeDkH|mnCNLMUsnO2bSc|NRoT(qdv1k zL?&EF_@wma`QG^qnGC6U`)-OJ?A18O7`t@xRpmU98jG(D`8n0sR+(Sf@wNFy=@FV-6nCa^?SB@_hq%%&(_tRx2iwrzrXtR*FkO7 zKNokb(fQAWnlxpmCRM2clcXjP^8s-{PGV(RW@=6fBL5U=dg=K4obvp7#`|>mrl1vU zU5u`uyS*FdyogX(KW()bFtIZ-i7?A pp1%-U^B8gEHgq%40|H?NCkuM81bDNuf#g|$@FS3p2C86S004m&DMkPQ literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 2470c320cc71..f44daa1ecbb5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -7,6 +7,7 @@ using System.Text; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -24,6 +25,7 @@ using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; using osu.Game.Skinning.Components; +using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -384,73 +386,82 @@ public void TestCopyPaste() [Test] public void TestMigrationArgon() { - AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded); - AddStep("add combo to global hud target", () => - { - globalHUDTarget.Add(new ArgonComboCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - }); + Live importedSkin = null!; - Live modifiedSkin = null!; + AddStep("import old argon skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"argon-layout-version-0.osk").SkinInfo); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); - AddStep("select another skin", () => - { - modifiedSkin = skins.CurrentSkinInfo.Value; - skins.CurrentSkinInfo.SetDefault(); - }); - AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0)); - AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin); - AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is ArgonComboCounter)); - AddAssert("ruleset hud target contains both combos", () => + AddStep("add combo to global target", () => globalHUDTarget.Add(new ArgonComboCounter { - var target = rulesetHUDTarget; - - return target.Components.Count == 2 && - target.Components[0] is ArgonComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft && - target.Components[1] is ArgonComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre; - }); - AddStep("save skin", () => skinEditor.Save()); - AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2f), + })); + AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value)); + + AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault()); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); } [Test] - public void TestMigrationLegacy() + public void TestMigrationTriangles() { - AddStep("select legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo); + Live importedSkin = null!; - AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded); - AddStep("add combo to global hud target", () => + AddStep("import old triangles skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"triangles-layout-version-0.osk").SkinInfo); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + + AddStep("add combo to global target", () => globalHUDTarget.Add(new DefaultComboCounter { - globalHUDTarget.Add(new LegacyComboCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - }); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2f), + })); + AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value)); + + AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault()); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + } - Live modifiedSkin = null!; + [Test] + public void TestMigrationLegacy() + { + Live importedSkin = null!; - AddStep("select another skin", () => - { - modifiedSkin = skins.CurrentSkinInfo.Value; - skins.CurrentSkinInfo.SetDefault(); - }); - AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0)); - AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin); - AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is LegacyComboCounter)); - AddAssert("ruleset hud target contains both combos", () => + AddStep("import old classic skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"classic-layout-version-0.osk").SkinInfo); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + + AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyComboCounter { - var target = rulesetHUDTarget; + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2f), + })); + AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value)); + + AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault()); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + } - return target.Components.Count == 2 && - target.Components[0] is LegacyComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft && - target.Components[1] is LegacyComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre; - }); - AddStep("save skin", () => skinEditor.Save()); - AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION); + private Skin importSkinFromArchives(string filename) + { + var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); + return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); } protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 606bc5e10cab..1ece81be5095 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -93,9 +93,8 @@ public class RealmAccess : IDisposable /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. - /// 42 2024-06-25 Add SkinInfo.LayoutVersion to allow performing migrations of components on structural changes. /// - private const int schema_version = 42; + private const int schema_version = 41; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 4cd54c06f073..707281db3107 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -12,7 +12,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; @@ -70,28 +69,6 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) // Purple new Color4(92, 0, 241, 255), }; - - if (skin.LayoutVersion < 20240625 - && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout) - && hudLayout.TryGetDrawableInfo(null, out var hudComponents)) - { - var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(ArgonComboCounter)).ToArray(); - - if (comboCounters.Any()) - { - hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray()); - - resources.RealmAccess.Run(r => - { - foreach (var ruleset in r.All()) - { - hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents) - ? rulesetComponents.Concat(comboCounters).ToArray() - : comboCounters); - } - }); - } - } } public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f148bad96e75..b71b626b4ed0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,7 +19,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -57,28 +56,6 @@ public LegacySkin(SkinInfo skin, IStorageResourceProvider resources) protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore, string configurationFilename = @"skin.ini") : base(skin, resources, fallbackStore, configurationFilename) { - if (resources != null - && skin.LayoutVersion < 20240625 - && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout) - && hudLayout.TryGetDrawableInfo(null, out var hudComponents)) - { - var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(LegacyComboCounter)).ToArray(); - - if (comboCounters.Any()) - { - hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray()); - - resources.RealmAccess.Run(r => - { - foreach (var ruleset in r.All()) - { - hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents) - ? rulesetComponents.Concat(comboCounters).ToArray() - : comboCounters); - } - }); - } - } } protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index e4ca908d901e..5bac5c3d81cb 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -21,11 +21,15 @@ using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; +using osu.Game.Rulesets; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { public abstract class Skin : IDisposable, ISkin { + private readonly IStorageResourceProvider? resources; + /// /// A texture store which can be used to perform user file lookups for this skin. /// @@ -68,6 +72,8 @@ public abstract class Skin : IDisposable, ISkin /// An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini". protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore = null, string configurationFilename = @"skin.ini") { + this.resources = resources; + Name = skin.Name; if (resources != null) @@ -131,40 +137,9 @@ protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStor { string jsonContent = Encoding.UTF8.GetString(bytes); - SkinLayoutInfo? layoutInfo = null; - - // handle namespace changes... - 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"); - jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter"); - - try - { - // First attempt to deserialise using the new SkinLayoutInfo format - layoutInfo = JsonConvert.DeserializeObject(jsonContent); - } - catch - { - } - - // Of note, the migration code below runs on read of skins, but there's nothing to - // force a rewrite after migration. Let's not remove these migration rules until we - // have something in place to ensure we don't end up breaking skins of users that haven't - // manually saved their skin since a change was implemented. - - // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list. + var layoutInfo = parseLayoutInfo(jsonContent, skinnableTarget); if (layoutInfo == null) - { - var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); - - if (deserializedContent == null) - continue; - - layoutInfo = new SkinLayoutInfo(); - layoutInfo.Update(null, deserializedContent.ToArray()); - - Logger.Log($"Ferrying {deserializedContent.Count()} components in {skinnableTarget} to global section of new {nameof(SkinLayoutInfo)} format"); - } + continue; LayoutInfos[skinnableTarget] = layoutInfo; } @@ -230,6 +205,81 @@ public void UpdateDrawableTarget(SkinComponentsContainer targetContainer) return null; } + #region Deserialisation & Migration + + private SkinLayoutInfo? parseLayoutInfo(string jsonContent, SkinComponentsContainerLookup.TargetArea target) + { + SkinLayoutInfo? layout = null; + + // handle namespace changes... + 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"); + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter"); + + try + { + // First attempt to deserialise using the new SkinLayoutInfo format + layout = JsonConvert.DeserializeObject(jsonContent); + } + catch + { + } + + // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list. + if (layout == null) + { + var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); + if (deserializedContent == null) + return null; + + layout = new SkinLayoutInfo { Version = 0 }; + layout.Update(null, deserializedContent.ToArray()); + + Logger.Log($"Ferrying {deserializedContent.Count()} components in {target} to global section of new {nameof(SkinLayoutInfo)} format"); + } + + for (int i = layout.Version + 1; i <= SkinLayoutInfo.LATEST_VERSION; i++) + applyMigration(layout, target, i); + + layout.Version = SkinLayoutInfo.LATEST_VERSION; + return layout; + } + + private void applyMigration(SkinLayoutInfo layout, SkinComponentsContainerLookup.TargetArea target, int version) + { + switch (version) + { + case 1: + { + if (target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents || + !layout.TryGetDrawableInfo(null, out var globalHUDComponents) || + resources == null) + break; + + var comboCounters = globalHUDComponents.Where(c => + c.Type.Name == nameof(LegacyComboCounter) || + c.Type.Name == nameof(DefaultComboCounter) || + c.Type.Name == nameof(ArgonComboCounter)).ToArray(); + + layout.Update(null, globalHUDComponents.Except(comboCounters).ToArray()); + + resources.RealmAccess.Run(r => + { + foreach (var ruleset in r.All()) + { + layout.Update(ruleset, layout.TryGetDrawableInfo(ruleset, out var rulesetHUDComponents) + ? rulesetHUDComponents.Concat(comboCounters).ToArray() + : comboCounters); + } + }); + + break; + } + } + } + + #endregion + #region Disposal ~Skin() diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 714427f40da6..59c7f0ba2679 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -223,8 +223,6 @@ public bool Save(Skin skin) } } - s.LayoutVersion = SkinInfo.LATEST_LAYOUT_VERSION; - string newHash = ComputeHash(s); hadChanges = newHash != s.Hash; diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index a3d5771b5e66..9763d3b57e9a 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -39,21 +39,6 @@ public class SkinInfo : RealmObject, IHasRealmFiles, IEquatable, IHasG public bool Protected { get; set; } - /// - /// The latest version in YYYYMMDD format for skin layout migrations. - /// - /// - /// - /// 20240625: Moves combo counters from ruleset-agnostic to ruleset-specific HUD targets. - /// - /// - public const int LATEST_LAYOUT_VERSION = 20240625; - - /// - /// A version in YYYYMMDD format for applying skin layout migrations. - /// - public int LayoutVersion { get; set; } - public virtual Skin CreateInstance(IStorageResourceProvider resources) { var type = string.IsNullOrEmpty(InstantiationInfo) diff --git a/osu.Game/Skinning/SkinLayoutInfo.cs b/osu.Game/Skinning/SkinLayoutInfo.cs index 115d59b9d044..22c876e5ad46 100644 --- a/osu.Game/Skinning/SkinLayoutInfo.cs +++ b/osu.Game/Skinning/SkinLayoutInfo.cs @@ -19,12 +19,26 @@ public class SkinLayoutInfo { private const string global_identifier = @"global"; - [JsonIgnore] - public IEnumerable AllDrawables => DrawableInfo.Values.SelectMany(v => v); + /// + /// Latest version representing the schema of the skin layout. + /// + /// + /// + /// 0: Initial version of all skin layouts. + /// 1: Moves existing combo counters from global to per-ruleset HUD targets. + /// + /// + public const int LATEST_VERSION = 1; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int Version = LATEST_VERSION; [JsonProperty] public Dictionary DrawableInfo { get; set; } = new Dictionary(); + [JsonIgnore] + public IEnumerable AllDrawables => DrawableInfo.Values.SelectMany(v => v); + public bool TryGetDrawableInfo(RulesetInfo? ruleset, [NotNullWhen(true)] out SerialisedDrawableInfo[]? components) => DrawableInfo.TryGetValue(ruleset?.ShortName ?? global_identifier, out components); From 3926af1053f5e4ef02b4caa7dcac1755d3658b3f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 20:17:39 +0200 Subject: [PATCH 052/579] Use draggable handle for length adjust --- .../TestSceneSliderSelectionBlueprint.cs | 31 +++-- .../Blueprints/Sliders/SliderCircleOverlay.cs | 127 +++++++++++++++++- .../Sliders/SliderSelectionBlueprint.cs | 57 ++++---- 3 files changed, 171 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 812b34dfe22e..c2589f11efc7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -3,7 +3,6 @@ #nullable disable -using System.Linq; using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -165,23 +164,35 @@ public void TestMultipleControlPointDeselectionViaClickOutside() } [Test] - public void TestAdjustDistance() + public void TestAdjustLength() { - AddStep("start adjust length", - () => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value()); - moveMouseToControlPoint(1); - AddStep("end adjust length", () => InputManager.Click(MouseButton.Right)); + AddStep("move mouse to drag marker", () => + { + Vector2 position = slider.Position + slider.Path.PositionAt(1) + new Vector2(60, 0); + InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); + }); + AddStep("start drag", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move mouse to control point 1", () => + { + Vector2 position = slider.Position + slider.Path.ControlPoints[1].Position + new Vector2(60, 0); + InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); + }); + AddStep("end adjust length", () => InputManager.ReleaseButton(MouseButton.Left)); AddAssert("expected distance halved", () => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1)); - AddStep("start adjust length", - () => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value()); + AddStep("move mouse to drag marker", () => + { + Vector2 position = slider.Position + slider.Path.PositionAt(1) + new Vector2(60, 0); + InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); + }); + AddStep("start drag", () => InputManager.PressButton(MouseButton.Left)); AddStep("move mouse beyond last control point", () => { - Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0); + Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(100, 0); InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position)); }); - AddStep("end adjust length", () => InputManager.Click(MouseButton.Right)); + AddStep("end adjust length", () => InputManager.ReleaseButton(MouseButton.Left)); AddAssert("expected distance is calculated distance", () => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1)); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index 55ea131dab2e..9752ce4a1373 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -1,24 +1,47 @@ // 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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public partial class SliderCircleOverlay : CompositeDrawable { + public RectangleF VisibleQuad + { + get + { + var result = CirclePiece.ScreenSpaceDrawQuad.AABBFloat; + + if (endDragMarkerContainer == null) return result; + + var size = result.Size * 1.4f; + var location = result.TopLeft - result.Size * 0.2f; + return new RectangleF(location, size); + } + } + protected readonly HitCirclePiece CirclePiece; - protected readonly Slider Slider; - private readonly HitCircleOverlapMarker marker; + private readonly Slider slider; private readonly SliderPosition position; + private readonly HitCircleOverlapMarker marker; + private readonly Container? endDragMarkerContainer; public SliderCircleOverlay(Slider slider, SliderPosition position) { - Slider = slider; + this.slider = slider; this.position = position; InternalChildren = new Drawable[] @@ -26,27 +49,121 @@ public SliderCircleOverlay(Slider slider, SliderPosition position) marker = new HitCircleOverlapMarker(), CirclePiece = new HitCirclePiece(), }; + + if (position == SliderPosition.End) + { + AddInternal(endDragMarkerContainer = new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding(-2.5f), + Child = EndDragMarker = new SliderEndDragMarker() + }); + } } + public SliderEndDragMarker? EndDragMarker { get; } + protected override void Update() { base.Update(); - var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : - Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat!; + var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : + slider.RepeatCount % 2 == 0 ? slider.TailCircle : slider.LastRepeat!; CirclePiece.UpdateFrom(circle); marker.UpdateFrom(circle); + + if (endDragMarkerContainer != null) + { + endDragMarkerContainer.Position = circle.Position; + endDragMarkerContainer.Scale = CirclePiece.Scale * 1.2f; + var diff = slider.Path.PositionAt(1) - slider.Path.PositionAt(0.99f); + endDragMarkerContainer.Rotation = float.RadiansToDegrees(MathF.Atan2(diff.Y, diff.X)); + } } public override void Hide() { CirclePiece.Hide(); + endDragMarkerContainer?.Hide(); } public override void Show() { CirclePiece.Show(); + endDragMarkerContainer?.Show(); + } + + public partial class SliderEndDragMarker : SmoothPath + { + public Action? StartDrag { get; set; } + public Action? Drag { get; set; } + public Action? EndDrag { get; set; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + var path = PathApproximator.CircularArcToPiecewiseLinear([ + new Vector2(0, OsuHitObject.OBJECT_RADIUS), + new Vector2(OsuHitObject.OBJECT_RADIUS, 0), + new Vector2(0, -OsuHitObject.OBJECT_RADIUS) + ]); + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + PathRadius = 5; + Vertices = path; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + updateState(); + StartDrag?.Invoke(e); + return true; + } + + protected override void OnDrag(DragEvent e) + { + updateState(); + base.OnDrag(e); + Drag?.Invoke(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + updateState(); + EndDrag?.Invoke(); + base.OnDragEnd(e); + } + + private void updateState() + { + Colour = IsHovered || IsDragged ? colours.Red : colours.Yellow; + } } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index eb269ba68032..87f9fd41e8e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -55,7 +55,18 @@ public partial class SliderSelectionBlueprint : OsuSelectionBlueprint [Resolved(CanBeNull = true)] private BindableBeatDivisor beatDivisor { get; set; } - public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad; + public override Quad SelectionQuad + { + get + { + var result = BodyPiece.ScreenSpaceDrawQuad.AABBFloat; + + result = RectangleF.Union(result, HeadOverlay.VisibleQuad); + result = RectangleF.Union(result, TailOverlay.VisibleQuad); + + return result; + } + } private readonly BindableList controlPoints = new BindableList(); private readonly IBindable pathVersion = new Bindable(); @@ -63,7 +74,6 @@ public partial class SliderSelectionBlueprint : OsuSelectionBlueprint // Cached slider path which ignored the expected distance value. private readonly Cached fullPathCache = new Cached(); - private bool isAdjustingLength; public SliderSelectionBlueprint(Slider slider) : base(slider) @@ -79,6 +89,10 @@ private void load() HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start), TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End), }; + + TailOverlay.EndDragMarker!.StartDrag += startAdjustingLength; + TailOverlay.EndDragMarker.Drag += adjustLength; + TailOverlay.EndDragMarker.EndDrag += endAdjustLength; } protected override void LoadComplete() @@ -141,9 +155,6 @@ protected override void OnDeselected() { base.OnDeselected(); - if (isAdjustingLength) - endAdjustLength(); - updateVisualDefinition(); BodyPiece.RecyclePath(); } @@ -173,12 +184,6 @@ private void updateVisualDefinition() protected override bool OnMouseDown(MouseDownEvent e) { - if (isAdjustingLength) - { - endAdjustLength(); - return true; - } - switch (e.Button) { case MouseButton.Right: @@ -202,18 +207,22 @@ protected override bool OnMouseDown(MouseDownEvent e) return false; } + private Vector2 lengthAdjustMouseOffset; + + private void startAdjustingLength(DragStartEvent e) + { + lengthAdjustMouseOffset = ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position - HitObject.Path.PositionAt(1); + changeHandler?.BeginChange(); + } + private void endAdjustLength() { trimExcessControlPoints(HitObject.Path); - isAdjustingLength = false; changeHandler?.EndChange(); } - protected override bool OnMouseMove(MouseMoveEvent e) + private void adjustLength(MouseEvent e) { - if (!isAdjustingLength) - return base.OnMouseMove(e); - double oldDistance = HitObject.Path.Distance; double proposedDistance = findClosestPathDistance(e); @@ -223,13 +232,11 @@ protected override bool OnMouseMove(MouseMoveEvent e) 10 * oldDistance / HitObject.SliderVelocityMultiplier); if (Precision.AlmostEquals(proposedDistance, oldDistance)) - return false; + return; HitObject.SliderVelocityMultiplier *= proposedDistance / oldDistance; HitObject.Path.ExpectedDistance.Value = proposedDistance; editorBeatmap?.Update(HitObject); - - return false; } /// @@ -262,12 +269,12 @@ private void trimExcessControlPoints(SliderPath sliderPath) /// /// Finds the expected distance value for which the slider end is closest to the mouse position. /// - private double findClosestPathDistance(MouseMoveEvent e) + private double findClosestPathDistance(MouseEvent e) { const double step1 = 10; const double step2 = 0.1; - var desiredPosition = e.MousePosition - HitObject.Position; + var desiredPosition = ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position - lengthAdjustMouseOffset; if (!fullPathCache.IsValid) fullPathCache.Value = new SliderPath(HitObject.Path.ControlPoints.ToArray()); @@ -525,11 +532,6 @@ private void convertToStream() addControlPoint(rightClickPosition); changeHandler?.EndChange(); }), - new OsuMenuItem("Adjust length", MenuItemType.Standard, () => - { - isAdjustingLength = true; - changeHandler?.BeginChange(); - }), new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream), }; @@ -544,9 +546,6 @@ private void convertToStream() public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { - if (isAdjustingLength) - return true; - if (BodyPiece.ReceivePositionalInputAt(screenSpacePos)) return true; From 5697c82bb87cc58460fe053f27039a5bb9dbaf84 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 20:33:00 +0200 Subject: [PATCH 053/579] add a small bias towards longer distances to prevent jittery behaviour on path self-intersections --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 87f9fd41e8e7..586ba5b6b1c7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; @@ -273,6 +274,7 @@ private double findClosestPathDistance(MouseEvent e) { const double step1 = 10; const double step2 = 0.1; + const double longer_distance_bias = 0.01; var desiredPosition = ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position - lengthAdjustMouseOffset; @@ -286,7 +288,7 @@ private double findClosestPathDistance(MouseEvent e) for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1) { double t = d / fullPathCache.Value.CalculatedDistance; - float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition); + double dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition) - d * longer_distance_bias; if (dist >= minDistance) continue; @@ -295,10 +297,12 @@ private double findClosestPathDistance(MouseEvent e) } // Do another linear search to fine-tune the result. - for (double d = bestValue - step1; d <= bestValue + step1; d += step2) + double maxValue = Math.Min(bestValue + step1, fullPathCache.Value.CalculatedDistance); + + for (double d = bestValue - step1; d <= maxValue; d += step2) { double t = d / fullPathCache.Value.CalculatedDistance; - float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition); + double dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition) - d * longer_distance_bias; if (dist >= minDistance) continue; From b9c6674a5885f6fda92acf72002d68f78006a47e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 4 Jul 2024 11:47:45 +0200 Subject: [PATCH 054/579] Allow seeking to sample point on double-click --- .../Components/Timeline/NodeSamplePointPiece.cs | 10 ++++++++++ .../Compose/Components/Timeline/SamplePointPiece.cs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index ae3838bc413e..e9999df76dda 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Extensions; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -22,6 +24,14 @@ public NodeSamplePointPiece(HitObject hitObject, int nodeIndex) NodeIndex = nodeIndex; } + protected override bool OnDoubleClick(DoubleClickEvent e) + { + var hasRepeats = (IHasRepeats)HitObject; + EditorClock?.SeekSmoothlyTo(HitObject.StartTime + hasRepeats.Duration * NodeIndex / hasRepeats.SpanCount()); + this.ShowPopover(); + return true; + } + protected override IList GetSamples() { var hasRepeats = (IHasRepeats)HitObject; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 930b78b4680f..0507f3d3d0bb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -32,6 +32,9 @@ public partial class SamplePointPiece : HitObjectPointPiece, IHasPopover { public readonly HitObject HitObject; + [Resolved] + protected EditorClock? EditorClock { get; private set; } + public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; @@ -54,6 +57,13 @@ protected override bool OnClick(ClickEvent e) return true; } + protected override bool OnDoubleClick(DoubleClickEvent e) + { + EditorClock?.SeekSmoothlyTo(HitObject.StartTime); + this.ShowPopover(); + return true; + } + private void updateText() { Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; From 00f7a34139f1a8c4d2e0112b23b7e26464893495 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 4 Jul 2024 15:25:43 +0200 Subject: [PATCH 055/579] Add test coverage --- .../TestSceneHitObjectSampleAdjustments.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 9988c1cb59a9..28bafb79ee70 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Input; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; @@ -307,6 +308,40 @@ public void TestNodeSamplePopover() hitObjectNodeHasSampleVolume(0, 1, 10); } + [Test] + public void TestSamplePointSeek() + { + AddStep("add slider", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new Slider + { + Position = new Vector2(256, 256), + StartTime = 0, + Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), + Samples = + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + }, + NodeSamples = + { + new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, + new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, + }, + RepeatCount = 1 + }); + }); + + doubleClickNodeSamplePiece(0, 0); + editorTimeIs(0); + doubleClickNodeSamplePiece(0, 1); + editorTimeIs(813); + doubleClickNodeSamplePiece(0, 2); + editorTimeIs(1627); + doubleClickSamplePiece(0); + editorTimeIs(0); + } + [Test] public void TestHotkeysMultipleSelectionWithSameSampleBank() { @@ -500,6 +535,24 @@ private void clickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"c InputManager.Click(MouseButton.Left); }); + private void doubleClickSamplePiece(int objectIndex) => AddStep($"double-click {objectIndex.ToOrdinalWords()} sample piece", () => + { + var samplePiece = this.ChildrenOfType().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); + + InputManager.MoveMouseTo(samplePiece); + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); + + private void doubleClickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"double-click {objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node sample piece", () => + { + var samplePiece = this.ChildrenOfType().Where(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)).ToArray()[nodeIndex]; + + InputManager.MoveMouseTo(samplePiece); + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); + private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () => { var popover = this.ChildrenOfType().SingleOrDefault(); @@ -644,5 +697,7 @@ private void hitObjectNodeHasSampleAdditionBank(int objectIndex, int nodeIndex, var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); }); + + private void editorTimeIs(double time) => AddAssert($"editor time is {time}", () => Precision.AlmostEquals(EditorClock.CurrentTimeAccurate, time, 1)); } } From fae8f5f81b4d2de10fe1e2f2e58f0258158a794b Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 4 Jul 2024 17:28:49 -0400 Subject: [PATCH 056/579] Refactor VeloUpdateManager --- osu.Desktop/Updater/VeloUpdateManager.cs | 47 +++++++++++------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/osu.Desktop/Updater/VeloUpdateManager.cs b/osu.Desktop/Updater/VeloUpdateManager.cs index 8fc68f77cd03..137e48f13587 100644 --- a/osu.Desktop/Updater/VeloUpdateManager.cs +++ b/osu.Desktop/Updater/VeloUpdateManager.cs @@ -10,12 +10,13 @@ using osu.Game.Overlays.Notifications; using osu.Game.Screens.Play; using osu.Game.Updater; +using Velopack.Sources; namespace osu.Desktop.Updater { public partial class VeloUpdateManager : UpdateManager { - private Velopack.UpdateManager? updateManager; + private readonly Velopack.UpdateManager updateManager; private INotificationOverlay notificationOverlay = null!; [Resolved] @@ -24,6 +25,12 @@ public partial class VeloUpdateManager : UpdateManager [Resolved] private ILocalUserPlayInfo? localUserInfo { get; set; } + public VeloUpdateManager() + { + const string? github_token = null; // TODO: populate. + updateManager = new Velopack.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false)); + } + [BackgroundDependencyLoader] private void load(INotificationOverlay notifications) { @@ -37,36 +44,30 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific // should we schedule a retry on completion of this check? bool scheduleRecheck = true; - const string? github_token = null; // TODO: populate. - try { // Avoid any kind of update checking while gameplay is running. if (localUserInfo?.IsPlaying.Value == true) return false; - updateManager ??= new Velopack.UpdateManager(new Velopack.Sources.GithubSource(@"https://github.com/ppy/osu", github_token, false)); - var info = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false); + // Handle no updates available. if (info == null) { - // If there is an update pending restart, show the notification again. - if (updateManager.IsUpdatePendingRestart) + // If there's no updates pending restart, bail and retry later. + if (!updateManager.IsUpdatePendingRestart) return false; + + // If there is an update pending restart, show the notification to restart again. + notificationOverlay.Post(new UpdateApplicationCompleteNotification { - notificationOverlay.Post(new UpdateApplicationCompleteNotification + Activated = () => { - Activated = () => - { - restartToApplyUpdate(); - return true; - } - }); - return true; - } - - // Otherwise there's no updates available. Bail and retry later. - return false; + restartToApplyUpdate(); + return true; + } + }); + return true; } scheduleRecheck = false; @@ -87,8 +88,6 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific { await updateManager.DownloadUpdatesAsync(info, p => notification.Progress = p / 100f).ConfigureAwait(false); - notification.StartInstall(); - notification.State = ProgressNotificationState.Completed; } catch (Exception e) @@ -98,10 +97,11 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific Logger.Error(e, @"update failed!"); } } - catch (Exception) + catch (Exception e) { // we'll ignore this and retry later. can be triggered by no internet connection or thread abortion. scheduleRecheck = true; + Logger.Error(e, @"update check failed!"); } finally { @@ -117,9 +117,6 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific private bool restartToApplyUpdate() { - if (updateManager == null) - return false; - updateManager.WaitExitThenApplyUpdates(null); Schedule(() => game.AttemptExit()); return true; From cae3607caf0d9322497c7be8c0bab4a9d2314cee Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 4 Jul 2024 17:30:42 -0400 Subject: [PATCH 057/579] Fix up restarting Earlier I changed the restarting logic to not wait until the program exits and instead try to facilitate restarting alone. This did not work, and it became clear we'd need Velopack to do the restarting. This reverts back and supposedly brings restarting logic in line with how Velopack does it --- osu.Desktop/OsuGameDesktop.cs | 13 +++---------- .../Screens/Setup/TournamentSwitcher.cs | 3 ++- osu.Game/OsuGameBase.cs | 2 +- .../Settings/Sections/Graphics/RendererSettings.cs | 3 ++- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 28f3d3dc5d1b..c0c8d1e5047e 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.Versioning; @@ -104,23 +103,17 @@ protected override UpdateManager CreateUpdateManager() return new VeloUpdateManager(); } - public override bool RestartApp() + public override bool RestartAppWhenExited() { try { - var startInfo = new ProcessStartInfo - { - FileName = Process.GetCurrentProcess().MainModule!.FileName, - UseShellExecute = true - }; - Process.Start(startInfo); - base.AttemptExit(); + Velopack.UpdateExe.Start(null, true); return true; } catch (Exception e) { Logger.Error(e, "Failed to restart application"); - return base.RestartApp(); + return base.RestartAppWhenExited(); } } diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs index 69ead451ba12..e55cbc2dbb4d 100644 --- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs +++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs @@ -31,7 +31,8 @@ private void load(TournamentStorage storage) Action = () => { - game.RestartApp(); + game.RestartAppWhenExited(); + game.AttemptExit(); }; folderButton.Action = () => storage.PresentExternally(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 573695613d1f..5e4ec5a61d93 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -513,7 +513,7 @@ public virtual void AttemptExit() /// If supported by the platform, the game will automatically restart after the next exit. /// /// Whether a restart operation was queued. - public virtual bool RestartApp() => false; + public virtual bool RestartAppWhenExited() => false; public bool Migrate(string path) { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 17e345c2c8dd..a8b127d5229a 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -67,8 +67,9 @@ private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, IDi if (r.NewValue == RendererType.Automatic && automaticRendererInUse) return; - if (game?.RestartApp() == true) + if (game?.RestartAppWhenExited() == true) { + game.AttemptExit(); } else { From c13f24d553a829b0d55ccdad733cee43d1509b5d Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 4 Jul 2024 17:32:30 -0400 Subject: [PATCH 058/579] Remove InstallingUpdate progress notification Velopack won't install the updates while the program is open, it'll do it in between restarts or before starting. --- osu.Game/Localisation/NotificationsStrings.cs | 5 ----- osu.Game/Updater/UpdateManager.cs | 6 ------ 2 files changed, 11 deletions(-) diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index 698fe230b294..d8f768f2d85e 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -135,11 +135,6 @@ public static class NotificationsStrings /// public static LocalisableString DownloadingUpdate => new TranslatableString(getKey(@"downloading_update"), @"Downloading update..."); - /// - /// "Installing update..." - /// - public static LocalisableString InstallingUpdate => new TranslatableString(getKey(@"installing_update"), @"Installing update..."); - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index bcb28d8b14e8..c114e3a8d057 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -176,12 +176,6 @@ public void StartDownload() Text = NotificationsStrings.DownloadingUpdate; } - public void StartInstall() - { - Progress = 0; - Text = NotificationsStrings.InstallingUpdate; - } - public void FailDownload() { State = ProgressNotificationState.Cancelled; From 461b791532ee9abebebc615d2e23f5a38f7324c8 Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 4 Jul 2024 17:32:56 -0400 Subject: [PATCH 059/579] Remove SimpleUpdateManager No longer needed. Velopack supports the platforms that this covers for --- osu.Game/Updater/SimpleUpdateManager.cs | 116 ------------------------ 1 file changed, 116 deletions(-) delete mode 100644 osu.Game/Updater/SimpleUpdateManager.cs diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs deleted file mode 100644 index 0f9d5b929f96..000000000000 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ /dev/null @@ -1,116 +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 System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using osu.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Platform; -using osu.Game.Online.API; -using osu.Game.Overlays.Notifications; - -namespace osu.Game.Updater -{ - /// - /// An update manager that shows notifications if a newer release is detected. - /// Installation is left up to the user. - /// - public partial class SimpleUpdateManager : UpdateManager - { - private string version = null!; - - [Resolved] - private GameHost host { get; set; } = null!; - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - version = game.Version; - } - - protected override async Task PerformUpdateCheck() - { - try - { - var releases = new OsuJsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest"); - - await releases.PerformAsync().ConfigureAwait(false); - - var latest = releases.ResponseObject; - - // avoid any discrepancies due to build suffixes for now. - // eventually we will want to support release streams and consider these. - version = version.Split('-').First(); - string latestTagName = latest.TagName.Split('-').First(); - - if (latestTagName != version && tryGetBestUrl(latest, out string? url)) - { - Notifications.Post(new SimpleNotification - { - Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n" - + "Click here to download the new version, which can be installed over the top of your existing installation", - Icon = FontAwesome.Solid.Download, - Activated = () => - { - host.OpenUrlExternally(url); - return true; - } - }); - - return true; - } - } - catch - { - // we shouldn't crash on a web failure. or any failure for the matter. - return true; - } - - return false; - } - - private bool tryGetBestUrl(GitHubRelease release, [NotNullWhen(true)] out string? url) - { - url = null; - GitHubAsset? bestAsset = null; - - switch (RuntimeInfo.OS) - { - case RuntimeInfo.Platform.Windows: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe", StringComparison.Ordinal)); - break; - - case RuntimeInfo.Platform.macOS: - string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "Apple.Silicon" : "Intel"; - bestAsset = release.Assets?.Find(f => f.Name.EndsWith($".app.{arch}.zip", StringComparison.Ordinal)); - break; - - case RuntimeInfo.Platform.Linux: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage", StringComparison.Ordinal)); - break; - - case RuntimeInfo.Platform.iOS: - if (release.Assets?.Exists(f => f.Name.EndsWith(".ipa", StringComparison.Ordinal)) == true) - // iOS releases are available via testflight. this link seems to work well enough for now. - // see https://stackoverflow.com/a/32960501 - url = "itms-beta://beta.itunes.apple.com/v1/app/1447765923"; - - break; - - case RuntimeInfo.Platform.Android: - if (release.Assets?.Exists(f => f.Name.EndsWith(".apk", StringComparison.Ordinal)) == true) - // on our testing device using the .apk URL causes the download to magically disappear. - url = release.HtmlUrl; - - break; - } - - url ??= bestAsset?.BrowserDownloadUrl; - return url != null; - } - } -} From 9e01cf7fc23812c4d0a76b09dd78ce6b38c06028 Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 4 Jul 2024 17:33:05 -0400 Subject: [PATCH 060/579] Move setupVelo logic higher up --- osu.Desktop/Program.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 7c23c15d5a06..92c8f2104c08 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -30,19 +30,9 @@ public static class Program [STAThread] public static void Main(string[] args) { - /* - * WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK! - * - * Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it. - * To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit, - * namely by checking loaded assemblies: - * https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32 - * - * If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded - - * the app will then do completely broken things like: - * - not creating system shortcuts (as the logic is if'd out if "running tests") - * - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests") - */ + // Velopack needs to run before anything else + setupVelo(); + if (OperatingSystem.IsWindows()) { var windowsVersion = Environment.OSVersion.Version; @@ -67,8 +57,6 @@ public static void Main(string[] args) } } - setupVelo(); - // NVIDIA profiles are based on the executable name of a process. // Lazer and stable share the same executable name. // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. From 6a0309294440dfd7136d2ceed72ca19e1be951eb Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 4 Jul 2024 17:45:34 -0400 Subject: [PATCH 061/579] Reformat --- osu.Desktop/Updater/VeloUpdateManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/Updater/VeloUpdateManager.cs b/osu.Desktop/Updater/VeloUpdateManager.cs index 137e48f13587..8aa580caa867 100644 --- a/osu.Desktop/Updater/VeloUpdateManager.cs +++ b/osu.Desktop/Updater/VeloUpdateManager.cs @@ -92,9 +92,9 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific } catch (Exception e) { - // In the case of an error, a separate notification will be displayed. - notification.FailDownload(); - Logger.Error(e, @"update failed!"); + // In the case of an error, a separate notification will be displayed. + notification.FailDownload(); + Logger.Error(e, @"update failed!"); } } catch (Exception e) From 72cf6bb12c71405464ce606a0334b2d6836c1bbc Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 4 Jul 2024 18:00:45 -0400 Subject: [PATCH 062/579] Allow downgrading Also better address UpdateManager conflict --- osu.Desktop/Updater/VeloUpdateManager.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Updater/VeloUpdateManager.cs b/osu.Desktop/Updater/VeloUpdateManager.cs index 8aa580caa867..6d3eb3f3f069 100644 --- a/osu.Desktop/Updater/VeloUpdateManager.cs +++ b/osu.Desktop/Updater/VeloUpdateManager.cs @@ -9,14 +9,15 @@ using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Screens.Play; -using osu.Game.Updater; +using Velopack; using Velopack.Sources; +using UpdateManager = Velopack.UpdateManager; namespace osu.Desktop.Updater { - public partial class VeloUpdateManager : UpdateManager + public partial class VeloUpdateManager : Game.Updater.UpdateManager { - private readonly Velopack.UpdateManager updateManager; + private readonly UpdateManager updateManager; private INotificationOverlay notificationOverlay = null!; [Resolved] @@ -28,7 +29,10 @@ public partial class VeloUpdateManager : UpdateManager public VeloUpdateManager() { const string? github_token = null; // TODO: populate. - updateManager = new Velopack.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false)); + updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), new UpdateOptions + { + AllowVersionDowngrade = true + }); } [BackgroundDependencyLoader] From 4898cff7a4186c3202cdef540c2480b48e22d55d Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Thu, 4 Jul 2024 18:25:02 -0400 Subject: [PATCH 063/579] Restart patch --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Desktop/osu.Desktop.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index c0c8d1e5047e..ee73c84ba372 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -107,7 +107,7 @@ public override bool RestartAppWhenExited() { try { - Velopack.UpdateExe.Start(null, true); + Velopack.UpdateExe.Start(); return true; } catch (Exception e) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 7df82e12813b..7a2bb599fd1b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From 71816c09dccf0a17932acb647e749190371e84a0 Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Fri, 5 Jul 2024 03:29:09 -0400 Subject: [PATCH 064/579] Resurrect SimpleUpdateManager as MobileUpdateNotifier While removing the desktop specific logic from it --- osu.Android/OsuGameAndroid.cs | 2 +- osu.Game/Updater/MobileUpdateNotifier.cs | 102 +++++++++++++++++++++++ osu.iOS/OsuGameIOS.cs | 2 +- 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Updater/MobileUpdateNotifier.cs diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index a235913ef3aa..ffab7dd86da2 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -80,7 +80,7 @@ public override void SetHost(GameHost host) host.Window.CursorState |= CursorState.Hidden; } - protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); + protected override UpdateManager CreateUpdateManager() => new MobileUpdateNotifier(); protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); diff --git a/osu.Game/Updater/MobileUpdateNotifier.cs b/osu.Game/Updater/MobileUpdateNotifier.cs new file mode 100644 index 000000000000..04b54df3c05d --- /dev/null +++ b/osu.Game/Updater/MobileUpdateNotifier.cs @@ -0,0 +1,102 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Platform; +using osu.Game.Online.API; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Updater +{ + /// + /// An update manager that shows notifications if a newer release is detected for mobile platforms. + /// Installation is left up to the user. + /// + public partial class MobileUpdateNotifier : UpdateManager + { + private string version = null!; + + [Resolved] + private GameHost host { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + version = game.Version; + } + + protected override async Task PerformUpdateCheck() + { + try + { + var releases = new OsuJsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest"); + + await releases.PerformAsync().ConfigureAwait(false); + + var latest = releases.ResponseObject; + + // avoid any discrepancies due to build suffixes for now. + // eventually we will want to support release streams and consider these. + version = version.Split('-').First(); + string latestTagName = latest.TagName.Split('-').First(); + + if (latestTagName != version && tryGetBestUrl(latest, out string? url)) + { + Notifications.Post(new SimpleNotification + { + Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n" + + "Click here to download the new version, which can be installed over the top of your existing installation", + Icon = FontAwesome.Solid.Download, + Activated = () => + { + host.OpenUrlExternally(url); + return true; + } + }); + + return true; + } + } + catch + { + // we shouldn't crash on a web failure. or any failure for the matter. + return true; + } + + return false; + } + + private bool tryGetBestUrl(GitHubRelease release, [NotNullWhen(true)] out string? url) + { + url = null; + GitHubAsset? bestAsset = null; + + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.iOS: + if (release.Assets?.Exists(f => f.Name.EndsWith(".ipa", StringComparison.Ordinal)) == true) + // iOS releases are available via testflight. this link seems to work well enough for now. + // see https://stackoverflow.com/a/32960501 + url = "itms-beta://beta.itunes.apple.com/v1/app/1447765923"; + + break; + + case RuntimeInfo.Platform.Android: + if (release.Assets?.Exists(f => f.Name.EndsWith(".apk", StringComparison.Ordinal)) == true) + // on our testing device using the .apk URL causes the download to magically disappear. + url = release.HtmlUrl; + + break; + } + + url ??= bestAsset?.BrowserDownloadUrl; + return url != null; + } + } +} diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 502f30215780..2a4f9b87ac6c 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -15,7 +15,7 @@ public partial class OsuGameIOS : OsuGame { public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); - protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); + protected override UpdateManager CreateUpdateManager() => new MobileUpdateNotifier(); protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); From 98610f4f6d1536842febaec22659bcf75b021872 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 5 Jul 2024 12:41:50 +0200 Subject: [PATCH 065/579] alt left/right or scroll to seek to neighbouring hit objects --- osu.Game/Screens/Edit/Editor.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c00b7ac4f2e8..c50cd09dd87f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -593,7 +593,7 @@ public void OnReleased(KeyBindingReleaseEvent e) protected override bool OnKeyDown(KeyDownEvent e) { - if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; + if (e.ControlPressed || e.SuperPressed) return false; switch (e.Key) { @@ -674,7 +674,7 @@ protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnScroll(ScrollEvent e) { - if (e.ControlPressed || e.AltPressed || e.SuperPressed) + if (e.ControlPressed || e.SuperPressed) return false; const double precision = 1; @@ -1064,8 +1064,24 @@ private void seekControlPoint(int direction) clock.Seek(found.Time); } + private void seekHitObject(int direction) + { + var found = direction < 1 + ? editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < clock.CurrentTimeAccurate) + : editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > clock.CurrentTimeAccurate); + + if (found != null) + clock.SeekSmoothlyTo(found.StartTime); + } + private void seek(UIEvent e, int direction) { + if (e.AltPressed) + { + seekHitObject(direction); + return; + } + double amount = e.ShiftPressed ? 4 : 1; bool trackPlaying = clock.IsRunning; From 7d6ade7e844df0abde9a4a25e0725c26db62d4d5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 5 Jul 2024 14:16:51 +0200 Subject: [PATCH 066/579] shift alt seek to open next sample edit popover --- .../Timeline/NodeSamplePointPiece.cs | 8 +-- .../Components/Timeline/SamplePointPiece.cs | 27 ++++++++- osu.Game/Screens/Edit/Editor.cs | 58 ++++++++++++++++++- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index e9999df76dda..1245d94a920f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Extensions; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -24,12 +22,10 @@ public NodeSamplePointPiece(HitObject hitObject, int nodeIndex) NodeIndex = nodeIndex; } - protected override bool OnDoubleClick(DoubleClickEvent e) + protected override double GetTime() { var hasRepeats = (IHasRepeats)HitObject; - EditorClock?.SeekSmoothlyTo(HitObject.StartTime + hasRepeats.Duration * NodeIndex / hasRepeats.SpanCount()); - this.ShowPopover(); - return true; + return HitObject.StartTime + hasRepeats.Duration * NodeIndex / hasRepeats.SpanCount(); } protected override IList GetSamples() diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 0507f3d3d0bb..8c05a8806e98 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Timing; using osuTK; using osuTK.Graphics; @@ -33,7 +34,10 @@ public partial class SamplePointPiece : HitObjectPointPiece, IHasPopover public readonly HitObject HitObject; [Resolved] - protected EditorClock? EditorClock { get; private set; } + private EditorClock? editorClock { get; set; } + + [Resolved] + private Editor? editor { get; set; } public SamplePointPiece(HitObject hitObject) { @@ -44,11 +48,30 @@ public SamplePointPiece(HitObject hitObject) protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Pink2 : colours.Pink1; + protected virtual double GetTime() => HitObject is IHasRepeats r ? HitObject.StartTime + r.Duration / r.SpanCount() / 2 : HitObject.StartTime; + [BackgroundDependencyLoader] private void load() { HitObject.DefaultsApplied += _ => updateText(); updateText(); + + if (editor != null) + editor.ShowSampleEditPopoverRequested += OnShowSampleEditPopoverRequested; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (editor != null) + editor.ShowSampleEditPopoverRequested -= OnShowSampleEditPopoverRequested; + } + + private void OnShowSampleEditPopoverRequested(double time) + { + if (time == GetTime()) + this.ShowPopover(); } protected override bool OnClick(ClickEvent e) @@ -59,7 +82,7 @@ protected override bool OnClick(ClickEvent e) protected override bool OnDoubleClick(DoubleClickEvent e) { - EditorClock?.SeekSmoothlyTo(HitObject.StartTime); + editorClock?.SeekSmoothlyTo(GetTime()); this.ShowPopover(); return true; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c50cd09dd87f..973908dfcbe9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -43,6 +43,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -1074,11 +1075,66 @@ private void seekHitObject(int direction) clock.SeekSmoothlyTo(found.StartTime); } + [CanBeNull] + public event Action ShowSampleEditPopoverRequested; + + private void seekSamplePoint(int direction) + { + double currentTime = clock.CurrentTimeAccurate; + + var current = direction < 1 + ? editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime < currentTime && r.EndTime >= currentTime) + : editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime <= currentTime && r.EndTime > currentTime); + + if (current == null) + { + if (direction < 1) + { + current = editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < currentTime); + if (current != null) + clock.SeekSmoothlyTo(current is IHasRepeats r ? r.EndTime : current.StartTime); + } + else + { + current = editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > currentTime); + if (current != null) + clock.SeekSmoothlyTo(current.StartTime); + } + } + else + { + // Find the next node sample point + var r = (IHasRepeats)current; + double[] nodeSamplePointTimes = new double[r.RepeatCount + 3]; + + nodeSamplePointTimes[0] = current.StartTime; + // The sample point for the main samples is sandwiched between the head and the first repeat + nodeSamplePointTimes[1] = current.StartTime + r.Duration / r.SpanCount() / 2; + + for (int i = 0; i < r.SpanCount(); i++) + { + nodeSamplePointTimes[i + 2] = current.StartTime + r.Duration / r.SpanCount() * (i + 1); + } + + double found = direction < 1 + ? nodeSamplePointTimes.Last(p => p < currentTime) + : nodeSamplePointTimes.First(p => p > currentTime); + + clock.SeekSmoothlyTo(found); + } + + // Show the sample edit popover at the current time + ShowSampleEditPopoverRequested?.Invoke(clock.CurrentTimeAccurate); + } + private void seek(UIEvent e, int direction) { if (e.AltPressed) { - seekHitObject(direction); + if (e.ShiftPressed) + seekSamplePoint(direction); + else + seekHitObject(direction); return; } From 8d46d6c6976039975414e34b54678293f2f9b574 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 5 Jul 2024 14:18:17 +0200 Subject: [PATCH 067/579] always seek on click --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 8c05a8806e98..a3c781260df3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -75,12 +75,6 @@ private void OnShowSampleEditPopoverRequested(double time) } protected override bool OnClick(ClickEvent e) - { - this.ShowPopover(); - return true; - } - - protected override bool OnDoubleClick(DoubleClickEvent e) { editorClock?.SeekSmoothlyTo(GetTime()); this.ShowPopover(); From c05f48979bec3377c68fc462d17a95ce87c9a35d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 5 Jul 2024 14:33:05 +0200 Subject: [PATCH 068/579] fix naming violation --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index a3c781260df3..731fe8ae6a7b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -57,7 +57,7 @@ private void load() updateText(); if (editor != null) - editor.ShowSampleEditPopoverRequested += OnShowSampleEditPopoverRequested; + editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested; } protected override void Dispose(bool isDisposing) @@ -65,10 +65,10 @@ protected override void Dispose(bool isDisposing) base.Dispose(isDisposing); if (editor != null) - editor.ShowSampleEditPopoverRequested -= OnShowSampleEditPopoverRequested; + editor.ShowSampleEditPopoverRequested -= onShowSampleEditPopoverRequested; } - private void OnShowSampleEditPopoverRequested(double time) + private void onShowSampleEditPopoverRequested(double time) { if (time == GetTime()) this.ShowPopover(); From 9013c119ab586684e74a2d94aabd1c522a17f4b9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 5 Jul 2024 14:33:15 +0200 Subject: [PATCH 069/579] update tests --- .../TestSceneHitObjectSampleAdjustments.cs | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 28bafb79ee70..af68948bb780 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -332,14 +332,29 @@ public void TestSamplePointSeek() }); }); - doubleClickNodeSamplePiece(0, 0); + clickNodeSamplePiece(0, 0); editorTimeIs(0); - doubleClickNodeSamplePiece(0, 1); + clickNodeSamplePiece(0, 1); editorTimeIs(813); - doubleClickNodeSamplePiece(0, 2); + clickNodeSamplePiece(0, 2); editorTimeIs(1627); - doubleClickSamplePiece(0); + clickSamplePiece(0); + editorTimeIs(406); + + seekSamplePiece(-1); editorTimeIs(0); + samplePopoverIsOpen(); + seekSamplePiece(-1); + editorTimeIs(0); + samplePopoverIsOpen(); + seekSamplePiece(1); + editorTimeIs(406); + seekSamplePiece(1); + editorTimeIs(813); + seekSamplePiece(1); + editorTimeIs(1627); + seekSamplePiece(1); + editorTimeIs(1627); } [Test] @@ -521,7 +536,7 @@ public void TestHotkeysAffectNodeSamples() private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => { - var samplePiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); + var samplePiece = this.ChildrenOfType().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); InputManager.MoveMouseTo(samplePiece); InputManager.Click(MouseButton.Left); @@ -535,22 +550,19 @@ private void clickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"c InputManager.Click(MouseButton.Left); }); - private void doubleClickSamplePiece(int objectIndex) => AddStep($"double-click {objectIndex.ToOrdinalWords()} sample piece", () => + private void seekSamplePiece(int direction) => AddStep($"seek sample piece {direction}", () => { - var samplePiece = this.ChildrenOfType().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); - - InputManager.MoveMouseTo(samplePiece); - InputManager.Click(MouseButton.Left); - InputManager.Click(MouseButton.Left); + InputManager.PressKey(Key.ShiftLeft); + InputManager.PressKey(Key.AltLeft); + InputManager.Key(direction < 1 ? Key.Left : Key.Right); + InputManager.ReleaseKey(Key.AltLeft); + InputManager.ReleaseKey(Key.ShiftLeft); }); - private void doubleClickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"double-click {objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node sample piece", () => + private void samplePopoverIsOpen() => AddUntilStep("sample popover is open", () => { - var samplePiece = this.ChildrenOfType().Where(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)).ToArray()[nodeIndex]; - - InputManager.MoveMouseTo(samplePiece); - InputManager.Click(MouseButton.Left); - InputManager.Click(MouseButton.Left); + var popover = this.ChildrenOfType().SingleOrDefault(o => o.IsPresent); + return popover != null; }); private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () => From ba44757c86f6a36e0debb7b88a6ce7b06f162dff Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 5 Jul 2024 15:24:39 +0200 Subject: [PATCH 070/579] clarify logic --- osu.Game/Screens/Edit/Editor.cs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 973908dfcbe9..847ad3eba84c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1082,26 +1082,12 @@ private void seekSamplePoint(int direction) { double currentTime = clock.CurrentTimeAccurate; + // Check if we are currently inside a hit object with node samples, if so seek to the next node sample point var current = direction < 1 ? editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime < currentTime && r.EndTime >= currentTime) : editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime <= currentTime && r.EndTime > currentTime); - if (current == null) - { - if (direction < 1) - { - current = editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < currentTime); - if (current != null) - clock.SeekSmoothlyTo(current is IHasRepeats r ? r.EndTime : current.StartTime); - } - else - { - current = editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > currentTime); - if (current != null) - clock.SeekSmoothlyTo(current.StartTime); - } - } - else + if (current != null) { // Find the next node sample point var r = (IHasRepeats)current; @@ -1122,6 +1108,21 @@ private void seekSamplePoint(int direction) clock.SeekSmoothlyTo(found); } + else + { + if (direction < 1) + { + current = editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < currentTime); + if (current != null) + clock.SeekSmoothlyTo(current is IHasRepeats r ? r.EndTime : current.StartTime); + } + else + { + current = editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > currentTime); + if (current != null) + clock.SeekSmoothlyTo(current.StartTime); + } + } // Show the sample edit popover at the current time ShowSampleEditPopoverRequested?.Invoke(clock.CurrentTimeAccurate); From 5da8bb5becf461f571257c0bbd2041f7781e57cf Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 7 Jul 2024 21:33:27 +0200 Subject: [PATCH 071/579] prevent volume control from eating inputs --- osu.Game/Overlays/Volume/VolumeControlReceptor.cs | 8 ++++---- osu.Game/Overlays/VolumeOverlay.cs | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 4ddbc9dd4876..2e8d86d4c7fd 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -23,15 +23,15 @@ public bool OnPressed(KeyBindingPressEvent e) { case GlobalAction.DecreaseVolume: case GlobalAction.IncreaseVolume: - ActionRequested?.Invoke(e.Action); - return true; + return ActionRequested?.Invoke(e.Action) == true; case GlobalAction.ToggleMute: case GlobalAction.NextVolumeMeter: case GlobalAction.PreviousVolumeMeter: if (!e.Repeat) - ActionRequested?.Invoke(e.Action); - return true; + return ActionRequested?.Invoke(e.Action) == true; + + return false; } return false; diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 5470c70400a1..fa6e797c9cd9 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -120,14 +120,18 @@ public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false return true; case GlobalAction.NextVolumeMeter: - if (State.Value == Visibility.Visible) - volumeMeters.SelectNext(); + if (State.Value != Visibility.Visible) + return false; + + volumeMeters.SelectNext(); Show(); return true; case GlobalAction.PreviousVolumeMeter: - if (State.Value == Visibility.Visible) - volumeMeters.SelectPrevious(); + if (State.Value != Visibility.Visible) + return false; + + volumeMeters.SelectPrevious(); Show(); return true; From f36321a8ea74a599f77263ef4d127bc58263006b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 7 Jul 2024 21:33:43 +0200 Subject: [PATCH 072/579] allow alt scroll for volume in editor --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 847ad3eba84c..acb9b93114f2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -675,7 +675,7 @@ protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnScroll(ScrollEvent e) { - if (e.ControlPressed || e.SuperPressed) + if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; const double precision = 1; From 306dc37ab5159d825adf9d5db29d50ab491e1e83 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 9 Jul 2024 12:28:23 +0200 Subject: [PATCH 073/579] Make hit object and sample point seek keybinds configurable --- .../Input/Bindings/GlobalActionContainer.cs | 16 +++++++++++ .../GlobalActionKeyBindingStrings.cs | 20 ++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 27 ++++++++++++------- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index ef0c60cd2060..542073476f5b 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -147,6 +147,10 @@ public static IEnumerable GetGlobalActionsFor(GlobalActionCategory new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), + new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject), + new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject), + new KeyBinding(new[] { InputKey.Alt, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint), + new KeyBinding(new[] { InputKey.Alt, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), }; private static IEnumerable editorTestPlayKeyBindings => new[] @@ -456,6 +460,18 @@ public enum GlobalAction [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToCurrentTime))] EditorTestPlayQuickExitToCurrentTime, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousHitObject))] + EditorSeekToPreviousHitObject, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextHitObject))] + EditorSeekToNextHitObject, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousSamplePoint))] + EditorSeekToPreviousSamplePoint, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextSamplePoint))] + EditorSeekToNextSamplePoint, } public enum GlobalActionCategory diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 450585f79abf..206db1a16674 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -404,6 +404,26 @@ public static class GlobalActionKeyBindingStrings /// public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed"); + /// + /// "Seek to previous hit object" + /// + public static LocalisableString EditorSeekToPreviousHitObject => new TranslatableString(getKey(@"editor_seek_to_previous_hit_object"), @"Seek to previous hit object"); + + /// + /// "Seek to next hit object" + /// + public static LocalisableString EditorSeekToNextHitObject => new TranslatableString(getKey(@"editor_seek_to_next_hit_object"), @"Seek to next hit object"); + + /// + /// "Seek to previous sample point" + /// + public static LocalisableString EditorSeekToPreviousSamplePoint => new TranslatableString(getKey(@"editor_seek_to_previous_sample_point"), @"Seek to previous sample point"); + + /// + /// "Seek to next sample point" + /// + public static LocalisableString EditorSeekToNextSamplePoint => new TranslatableString(getKey(@"editor_seek_to_next_sample_point"), @"Seek to next sample point"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index acb9b93114f2..214549a68d0c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -594,7 +594,7 @@ public void OnReleased(KeyBindingReleaseEvent e) protected override bool OnKeyDown(KeyDownEvent e) { - if (e.ControlPressed || e.SuperPressed) return false; + if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; switch (e.Key) { @@ -746,6 +746,22 @@ public bool OnPressed(KeyBindingPressEvent e) bottomBar.TestGameplayButton.TriggerClick(); return true; + case GlobalAction.EditorSeekToPreviousHitObject: + seekHitObject(-1); + return true; + + case GlobalAction.EditorSeekToNextHitObject: + seekHitObject(1); + return true; + + case GlobalAction.EditorSeekToPreviousSamplePoint: + seekSamplePoint(-1); + return true; + + case GlobalAction.EditorSeekToNextSamplePoint: + seekSamplePoint(1); + return true; + default: return false; } @@ -1130,15 +1146,6 @@ private void seekSamplePoint(int direction) private void seek(UIEvent e, int direction) { - if (e.AltPressed) - { - if (e.ShiftPressed) - seekSamplePoint(direction); - else - seekHitObject(direction); - return; - } - double amount = e.ShiftPressed ? 4 : 1; bool trackPlaying = clock.IsRunning; From 0bc14ba646e8fc7adfa9d8e3b53d0bf1341239e0 Mon Sep 17 00:00:00 2001 From: Layendan Date: Wed, 17 Jul 2024 12:45:20 -0700 Subject: [PATCH 074/579] Add favourite button to results screen --- osu.Game/Screens/Ranking/FavouriteButton.cs | 145 ++++++++++++++++++++ osu.Game/Screens/Ranking/ResultsScreen.cs | 25 +++- 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Ranking/FavouriteButton.cs diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs new file mode 100644 index 000000000000..ee093d343eda --- /dev/null +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -0,0 +1,145 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Logging; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; +using osuTK; + +namespace osu.Game.Screens.Ranking +{ + public partial class FavouriteButton : OsuAnimatedButton + { + private readonly Box background; + private readonly SpriteIcon icon; + private readonly BindableWithCurrent current; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly APIBeatmapSet beatmapSet; + + private PostBeatmapFavouriteRequest favouriteRequest; + private LoadingLayer loading; + + private readonly IBindable localUser = new Bindable(); + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + public FavouriteButton(APIBeatmapSet beatmapSet) + { + this.beatmapSet = beatmapSet; + current = new BindableWithCurrent(new BeatmapSetFavouriteState(this.beatmapSet.HasFavourited, this.beatmapSet.FavouriteCount)); + + Size = new Vector2(50, 30); + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(13), + Icon = FontAwesome.Regular.Heart, + }, + loading = new LoadingLayer(true, false), + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, IAPIProvider api) + { + this.api = api; + + updateState(); + + localUser.BindTo(api.LocalUser); + localUser.BindValueChanged(_ => updateEnabled()); + + Action = () => toggleFavouriteStatus(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Action = toggleFavouriteStatus; + current.BindValueChanged(_ => updateState(), true); + } + + private void toggleFavouriteStatus() + { + + Enabled.Value = false; + loading.Show(); + + var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; + + favouriteRequest?.Cancel(); + favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType); + + favouriteRequest.Success += () => + { + bool favourited = actionType == BeatmapFavouriteAction.Favourite; + + current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1)); + + Enabled.Value = true; + loading.Hide(); + }; + favouriteRequest.Failure += e => + { + Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}"); + Enabled.Value = true; + loading.Hide(); + }; + + api.Queue(favouriteRequest); + } + + private void updateEnabled() => Enabled.Value = !(localUser.Value is GuestUser) && beatmapSet.OnlineID > 0; + + private void updateState() + { + if (current?.Value == null) + return; + + if (current.Value.Favourited) + { + background.Colour = colours.Green; + icon.Icon = FontAwesome.Solid.Heart; + TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite; + } + else + { + background.Colour = colours.Gray4; + icon.Icon = FontAwesome.Regular.Heart; + TooltipText = BeatmapsetsStrings.ShowDetailsFavourite; + } + } + } +} diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44b270db5392..e96265be3d51 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -22,6 +23,7 @@ using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Placeholders; using osu.Game.Overlays; using osu.Game.Scoring; @@ -76,7 +78,7 @@ public abstract partial class ResultsScreen : ScreenWithBeatmapBackground, IKeyB /// /// Whether the user's personal statistics should be shown on the extended statistics panel - /// after clicking the score panel associated with the being presented. + /// after clicking the score panel associated with the being presented. /// Requires to be present. /// public bool ShowUserStatistics { get; init; } @@ -202,6 +204,27 @@ private void load(AudioManager audio) }, }); } + + // Do not render if user is not logged in or the mapset does not have a valid online ID. + if (api.IsLoggedIn && Score?.BeatmapInfo?.BeatmapSet != null && Score.BeatmapInfo.BeatmapSet.OnlineID > 0) + { + GetBeatmapSetRequest beatmapSetRequest; + beatmapSetRequest = new GetBeatmapSetRequest(Score.BeatmapInfo.BeatmapSet.OnlineID); + + beatmapSetRequest.Success += (beatmapSet) => + { + buttons.Add(new FavouriteButton(beatmapSet) + { + Width = 75 + }); + }; + beatmapSetRequest.Failure += e => + { + Logger.Error(e, $"Failed to fetch beatmap info: {e.Message}"); + }; + + api.Queue(beatmapSetRequest); + } } protected override void LoadComplete() From 3296beb00362a55e012f6b809d901da02174552b Mon Sep 17 00:00:00 2001 From: Layendan Date: Sat, 20 Jul 2024 11:49:46 -0700 Subject: [PATCH 075/579] Added collection button to result screen --- osu.Game/Screens/Ranking/CollectionButton.cs | 64 +++++++++++ osu.Game/Screens/Ranking/CollectionPopover.cs | 70 ++++++++++++ osu.Game/Screens/Ranking/FavouriteButton.cs | 7 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 108 ++++++++++-------- 4 files changed, 196 insertions(+), 53 deletions(-) create mode 100644 osu.Game/Screens/Ranking/CollectionButton.cs create mode 100644 osu.Game/Screens/Ranking/CollectionPopover.cs diff --git a/osu.Game/Screens/Ranking/CollectionButton.cs b/osu.Game/Screens/Ranking/CollectionButton.cs new file mode 100644 index 000000000000..99a51e03d963 --- /dev/null +++ b/osu.Game/Screens/Ranking/CollectionButton.cs @@ -0,0 +1,64 @@ +// 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.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Screens.Ranking +{ + public partial class CollectionButton : OsuAnimatedButton, IHasPopover + { + private readonly Box background; + + private readonly BeatmapInfo beatmapInfo; + + public CollectionButton(BeatmapInfo beatmapInfo) + { + this.beatmapInfo = beatmapInfo; + + Size = new Vector2(50, 30); + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(13), + Icon = FontAwesome.Solid.Book, + }, + }; + + TooltipText = "collections"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Green; + + Action = this.ShowPopover; + } + + // use Content for tracking input as some buttons might be temporarily hidden with DisappearToBottom, and they become hidden by moving Content away from screen. + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); + + public Popover GetPopover() => new CollectionPopover(beatmapInfo); + } +} diff --git a/osu.Game/Screens/Ranking/CollectionPopover.cs b/osu.Game/Screens/Ranking/CollectionPopover.cs new file mode 100644 index 000000000000..926745d4d95b --- /dev/null +++ b/osu.Game/Screens/Ranking/CollectionPopover.cs @@ -0,0 +1,70 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.Database; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Screens.Ranking +{ + public partial class CollectionPopover : OsuPopover + { + private OsuMenu menu; + private readonly BeatmapInfo beatmapInfo; + + [Resolved] + private RealmAccess realm { get; set; } = null!; + [Resolved] + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } + + public CollectionPopover(BeatmapInfo beatmapInfo) : base(false) + { + this.beatmapInfo = beatmapInfo; + } + + [BackgroundDependencyLoader] + private void load() + { + Margin = new MarginPadding(5); + Body.CornerRadius = 4; + + Children = new[] + { + menu = new OsuMenu(Direction.Vertical, true) + { + Items = items, + }, + }; + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + Hide(); + } + + private OsuMenuItem[] items + { + get + { + var collectionItems = realm.Realm.All() + .OrderBy(c => c.Name) + .AsEnumerable() + .Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmapInfo)).Cast().ToList(); + + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + + return collectionItems.ToArray(); + } + } + } +} diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index ee093d343eda..60149292429c 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -71,23 +71,20 @@ public FavouriteButton(APIBeatmapSet beatmapSet) } [BackgroundDependencyLoader] - private void load(OsuColour colours, IAPIProvider api) + private void load() { - this.api = api; - updateState(); localUser.BindTo(api.LocalUser); localUser.BindValueChanged(_ => updateEnabled()); - Action = () => toggleFavouriteStatus(); + Action = toggleFavouriteStatus; } protected override void LoadComplete() { base.LoadComplete(); - Action = toggleFavouriteStatus; current.BindValueChanged(_ => updateState(), true); } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e96265be3d51..b88a3cd2f815 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -99,73 +100,79 @@ private void load(AudioManager audio) popInSample = audio.Samples.Get(@"UI/overlay-pop-in"); - InternalChild = new GridContainer + InternalChild = new PopoverContainer { + Depth = -1, RelativeSizeAxes = Axes.Both, - Content = new[] + Padding = new MarginPadding(0), + Child = new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + Content = new[] { - VerticalScrollContent = new VerticalScrollContainer + new Drawable[] { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new Container + VerticalScrollContent = new VerticalScrollContainer { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + ScrollbarVisible = false, + Child = new Container { - StatisticsPanel = createStatisticsPanel().With(panel => + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - panel.RelativeSizeAxes = Axes.Both; - panel.Score.BindTarget = SelectedScore; - }), - ScorePanelList = new ScorePanelList + StatisticsPanel = createStatisticsPanel().With(panel => + { + panel.RelativeSizeAxes = Axes.Both; + panel.Score.BindTarget = SelectedScore; + }), + ScorePanelList = new ScorePanelList + { + RelativeSizeAxes = Axes.Both, + SelectedScore = { BindTarget = SelectedScore }, + PostExpandAction = () => StatisticsPanel.ToggleVisibility() + }, + detachedPanelContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + } + } + }, + }, + new[] + { + bottomPanel = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = TwoLayerButton.SIZE_EXTENDED.Y, + Alpha = 0, + Children = new Drawable[] + { + new Box { RelativeSizeAxes = Axes.Both, - SelectedScore = { BindTarget = SelectedScore }, - PostExpandAction = () => StatisticsPanel.ToggleVisibility() + Colour = Color4Extensions.FromHex("#333") }, - detachedPanelContainer = new Container + buttons = new FillFlowContainer { - RelativeSizeAxes = Axes.Both + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + Direction = FillDirection.Horizontal }, } } - }, + } }, - new[] + RowDimensions = new[] { - bottomPanel = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = TwoLayerButton.SIZE_EXTENDED.Y, - Alpha = 0, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#333") - }, - buttons = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - Direction = FillDirection.Horizontal - } - } - } + new Dimension(), + new Dimension(GridSizeMode.AutoSize) } - }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize) } }; @@ -205,6 +212,11 @@ private void load(AudioManager audio) }); } + if (Score?.BeatmapInfo != null) + { + buttons.Add(new CollectionButton(Score.BeatmapInfo) { Width = 75 }); + } + // Do not render if user is not logged in or the mapset does not have a valid online ID. if (api.IsLoggedIn && Score?.BeatmapInfo?.BeatmapSet != null && Score.BeatmapInfo.BeatmapSet.OnlineID > 0) { From c16b7c5c707f62be237d280fd477101532dbf8a8 Mon Sep 17 00:00:00 2001 From: Layendan Date: Sun, 21 Jul 2024 10:01:06 -0700 Subject: [PATCH 076/579] Update favorite button --- osu.Game/Screens/Ranking/FavouriteButton.cs | 62 ++++++++++++++------- osu.Game/Screens/Ranking/ResultsScreen.cs | 23 ++------ 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index 60149292429c..5a8cd51c6587 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -24,15 +25,10 @@ public partial class FavouriteButton : OsuAnimatedButton { private readonly Box background; private readonly SpriteIcon icon; - private readonly BindableWithCurrent current; - public Bindable Current - { - get => current.Current; - set => current.Current = value; - } - - private readonly APIBeatmapSet beatmapSet; + private readonly BeatmapSetInfo beatmapSetInfo; + private APIBeatmapSet beatmapSet; + private Bindable current; private PostBeatmapFavouriteRequest favouriteRequest; private LoadingLayer loading; @@ -45,10 +41,9 @@ public Bindable Current [Resolved] private OsuColour colours { get; set; } - public FavouriteButton(APIBeatmapSet beatmapSet) + public FavouriteButton(BeatmapSetInfo beatmapSetInfo) { - this.beatmapSet = beatmapSet; - current = new BindableWithCurrent(new BeatmapSetFavouriteState(this.beatmapSet.HasFavourited, this.beatmapSet.FavouriteCount)); + this.beatmapSetInfo = beatmapSetInfo; Size = new Vector2(50, 30); @@ -68,24 +63,42 @@ public FavouriteButton(APIBeatmapSet beatmapSet) }, loading = new LoadingLayer(true, false), }; + + Action = toggleFavouriteStatus; } [BackgroundDependencyLoader] private void load() { - updateState(); + current = new BindableWithCurrent(new BeatmapSetFavouriteState(false, 0)); + current.BindValueChanged(_ => updateState(), true); localUser.BindTo(api.LocalUser); - localUser.BindValueChanged(_ => updateEnabled()); - - Action = toggleFavouriteStatus; + localUser.BindValueChanged(_ => updateUser(), true); } - protected override void LoadComplete() + private void getBeatmapSet() { - base.LoadComplete(); + GetBeatmapSetRequest beatmapSetRequest; + beatmapSetRequest = new GetBeatmapSetRequest(beatmapSetInfo.OnlineID); - current.BindValueChanged(_ => updateState(), true); + loading.Show(); + beatmapSetRequest.Success += beatmapSet => + { + this.beatmapSet = beatmapSet; + current.Value = new BeatmapSetFavouriteState(this.beatmapSet.HasFavourited, this.beatmapSet.FavouriteCount); + + loading.Hide(); + Enabled.Value = true; + }; + beatmapSetRequest.Failure += e => + { + Logger.Error(e, $"Failed to fetch beatmap info: {e.Message}"); + + loading.Hide(); + Enabled.Value = false; + }; + api.Queue(beatmapSetRequest); } private void toggleFavouriteStatus() @@ -118,7 +131,18 @@ private void toggleFavouriteStatus() api.Queue(favouriteRequest); } - private void updateEnabled() => Enabled.Value = !(localUser.Value is GuestUser) && beatmapSet.OnlineID > 0; + private void updateUser() + { + if (!(localUser.Value is GuestUser) && beatmapSetInfo.OnlineID > 0) + getBeatmapSet(); + else + { + Enabled.Value = false; + current.Value = new BeatmapSetFavouriteState(false, 0); + updateState(); + TooltipText = BeatmapsetsStrings.ShowDetailsFavouriteLogin; + } + } private void updateState() { diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index b88a3cd2f815..befd024ccb67 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -24,7 +23,6 @@ using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Placeholders; using osu.Game.Overlays; using osu.Game.Scoring; @@ -217,25 +215,12 @@ private void load(AudioManager audio) buttons.Add(new CollectionButton(Score.BeatmapInfo) { Width = 75 }); } - // Do not render if user is not logged in or the mapset does not have a valid online ID. - if (api.IsLoggedIn && Score?.BeatmapInfo?.BeatmapSet != null && Score.BeatmapInfo.BeatmapSet.OnlineID > 0) + if (Score?.BeatmapInfo?.BeatmapSet != null && Score.BeatmapInfo.BeatmapSet.OnlineID > 0) { - GetBeatmapSetRequest beatmapSetRequest; - beatmapSetRequest = new GetBeatmapSetRequest(Score.BeatmapInfo.BeatmapSet.OnlineID); - - beatmapSetRequest.Success += (beatmapSet) => - { - buttons.Add(new FavouriteButton(beatmapSet) - { - Width = 75 - }); - }; - beatmapSetRequest.Failure += e => + buttons.Add(new FavouriteButton(Score.BeatmapInfo.BeatmapSet) { - Logger.Error(e, $"Failed to fetch beatmap info: {e.Message}"); - }; - - api.Queue(beatmapSetRequest); + Width = 75 + }); } } From a575566638fc65abba4f7baa91f57917b9b604e6 Mon Sep 17 00:00:00 2001 From: Layendan Date: Sun, 21 Jul 2024 16:14:26 -0700 Subject: [PATCH 077/579] Add tests --- .../Ranking/TestSceneCollectionButton.cs | 71 +++++++++++++++ .../Ranking/TestSceneFavouriteButton.cs | 90 +++++++++++++++++++ osu.Game/Screens/Ranking/FavouriteButton.cs | 14 +-- 3 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs new file mode 100644 index 000000000000..7bc2964cdf4d --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Screens.Ranking; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Ranking +{ + public partial class TestSceneCollectionButton : OsuManualInputManagerTestScene + { + private CollectionButton collectionButton; + private BeatmapInfo beatmapInfo = new BeatmapInfo { OnlineID = 88 }; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create button", () => Child = new PopoverContainer + { + Depth = -1, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = collectionButton = new CollectionButton(beatmapInfo) + { + RelativeSizeAxes = Axes.None, + Size = new Vector2(50), + } + }); + } + + [Test] + public void TestCollectionButton() + { + AddStep("click collection button", () => + { + InputManager.MoveMouseTo(collectionButton); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("collection popover is visible", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + + AddStep("click outside popover", () => + { + InputManager.MoveMouseTo(ScreenSpaceDrawQuad.TopLeft); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("collection popover is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + + AddStep("click collection button", () => + { + InputManager.MoveMouseTo(collectionButton); + InputManager.Click(MouseButton.Left); + }); + + AddStep("press escape", () => InputManager.Key(Key.Escape)); + + AddAssert("collection popover is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + } + } +} diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs new file mode 100644 index 000000000000..6ce9fdb87e42 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs @@ -0,0 +1,90 @@ +// 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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Ranking; +using osuTK; + +namespace osu.Game.Tests.Visual.Ranking +{ + public partial class TestSceneFavouriteButton : OsuTestScene + { + private FavouriteButton favourite; + + private readonly BeatmapSetInfo beatmapSetInfo = new BeatmapSetInfo { OnlineID = 88 }; + private readonly BeatmapSetInfo invalidBeatmapSetInfo = new BeatmapSetInfo(); + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create button", () => Child = favourite = new FavouriteButton(beatmapSetInfo) + { + RelativeSizeAxes = Axes.None, + Size = new Vector2(50), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + AddStep("register request handling", () => dummyAPI.HandleRequest = request => + { + if (!(request is GetBeatmapSetRequest beatmapSetRequest)) return false; + + beatmapSetRequest.TriggerSuccess(new APIBeatmapSet + { + OnlineID = beatmapSetRequest.ID, + HasFavourited = false, + FavouriteCount = 0, + }); + + return true; + }); + } + + [Test] + public void TestLoggedOutIn() + { + AddStep("log out", () => API.Logout()); + checkEnabled(false); + AddStep("log in", () => + { + API.Login("test", "test"); + ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh"); + }); + checkEnabled(true); + } + + [Test] + public void TestInvalidBeatmap() + { + AddStep("make beatmap invalid", () => Child = favourite = new FavouriteButton(invalidBeatmapSetInfo) + { + RelativeSizeAxes = Axes.None, + Size = new Vector2(50), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + AddStep("log in", () => + { + API.Login("test", "test"); + ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh"); + }); + checkEnabled(false); + } + + private void checkEnabled(bool expected) + { + AddAssert("is " + (expected ? "enabled" : "disabled"), () => favourite.Enabled.Value == expected); + } + } +} diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index 5a8cd51c6587..2f2da8ae40a4 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -26,12 +26,12 @@ public partial class FavouriteButton : OsuAnimatedButton private readonly Box background; private readonly SpriteIcon icon; - private readonly BeatmapSetInfo beatmapSetInfo; + public readonly BeatmapSetInfo BeatmapSetInfo; private APIBeatmapSet beatmapSet; - private Bindable current; + private readonly Bindable current; private PostBeatmapFavouriteRequest favouriteRequest; - private LoadingLayer loading; + private readonly LoadingLayer loading; private readonly IBindable localUser = new Bindable(); @@ -43,7 +43,8 @@ public partial class FavouriteButton : OsuAnimatedButton public FavouriteButton(BeatmapSetInfo beatmapSetInfo) { - this.beatmapSetInfo = beatmapSetInfo; + BeatmapSetInfo = beatmapSetInfo; + current = new BindableWithCurrent(new BeatmapSetFavouriteState(false, 0)); Size = new Vector2(50, 30); @@ -70,7 +71,6 @@ public FavouriteButton(BeatmapSetInfo beatmapSetInfo) [BackgroundDependencyLoader] private void load() { - current = new BindableWithCurrent(new BeatmapSetFavouriteState(false, 0)); current.BindValueChanged(_ => updateState(), true); localUser.BindTo(api.LocalUser); @@ -80,7 +80,7 @@ private void load() private void getBeatmapSet() { GetBeatmapSetRequest beatmapSetRequest; - beatmapSetRequest = new GetBeatmapSetRequest(beatmapSetInfo.OnlineID); + beatmapSetRequest = new GetBeatmapSetRequest(BeatmapSetInfo.OnlineID); loading.Show(); beatmapSetRequest.Success += beatmapSet => @@ -133,7 +133,7 @@ private void toggleFavouriteStatus() private void updateUser() { - if (!(localUser.Value is GuestUser) && beatmapSetInfo.OnlineID > 0) + if (!(localUser.Value is GuestUser) && BeatmapSetInfo.OnlineID > 0) getBeatmapSet(); else { From e4cccb5e319ed2f83d445a169250cebe0b085845 Mon Sep 17 00:00:00 2001 From: Layendan Date: Sun, 21 Jul 2024 17:32:48 -0700 Subject: [PATCH 078/579] Fix lint errors --- .../Visual/Ranking/TestSceneCollectionButton.cs | 2 +- .../Visual/Ranking/TestSceneFavouriteButton.cs | 1 - osu.Game/Screens/Ranking/CollectionPopover.cs | 15 ++++++++------- osu.Game/Screens/Ranking/FavouriteButton.cs | 4 +--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs index 7bc2964cdf4d..2cd75f6cef60 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Ranking public partial class TestSceneCollectionButton : OsuManualInputManagerTestScene { private CollectionButton collectionButton; - private BeatmapInfo beatmapInfo = new BeatmapInfo { OnlineID = 88 }; + private readonly BeatmapInfo beatmapInfo = new BeatmapInfo { OnlineID = 88 }; [SetUpSteps] public void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs index 6ce9fdb87e42..b281fc1bbf08 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs @@ -24,7 +24,6 @@ public partial class TestSceneFavouriteButton : OsuTestScene private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; - [SetUpSteps] public void SetUpSteps() { diff --git a/osu.Game/Screens/Ranking/CollectionPopover.cs b/osu.Game/Screens/Ranking/CollectionPopover.cs index 926745d4d95b..98a5de597e51 100644 --- a/osu.Game/Screens/Ranking/CollectionPopover.cs +++ b/osu.Game/Screens/Ranking/CollectionPopover.cs @@ -17,15 +17,16 @@ namespace osu.Game.Screens.Ranking { public partial class CollectionPopover : OsuPopover { - private OsuMenu menu; private readonly BeatmapInfo beatmapInfo; [Resolved] private RealmAccess realm { get; set; } = null!; + [Resolved] - private ManageCollectionsDialog? manageCollectionsDialog { get; set; } + private ManageCollectionsDialog manageCollectionsDialog { get; set; } - public CollectionPopover(BeatmapInfo beatmapInfo) : base(false) + public CollectionPopover(BeatmapInfo beatmapInfo) + : base(false) { this.beatmapInfo = beatmapInfo; } @@ -38,7 +39,7 @@ private void load() Children = new[] { - menu = new OsuMenu(Direction.Vertical, true) + new OsuMenu(Direction.Vertical, true) { Items = items, }, @@ -56,9 +57,9 @@ private OsuMenuItem[] items get { var collectionItems = realm.Realm.All() - .OrderBy(c => c.Name) - .AsEnumerable() - .Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmapInfo)).Cast().ToList(); + .OrderBy(c => c.Name) + .AsEnumerable() + .Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmapInfo)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index 2f2da8ae40a4..95e1fdf98540 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -79,8 +79,7 @@ private void load() private void getBeatmapSet() { - GetBeatmapSetRequest beatmapSetRequest; - beatmapSetRequest = new GetBeatmapSetRequest(BeatmapSetInfo.OnlineID); + GetBeatmapSetRequest beatmapSetRequest = new GetBeatmapSetRequest(BeatmapSetInfo.OnlineID); loading.Show(); beatmapSetRequest.Success += beatmapSet => @@ -103,7 +102,6 @@ private void getBeatmapSet() private void toggleFavouriteStatus() { - Enabled.Value = false; loading.Show(); From 6bb562db14ccd84ac27c45fe14623e9a443da7e4 Mon Sep 17 00:00:00 2001 From: Layendan Date: Sun, 21 Jul 2024 17:51:30 -0700 Subject: [PATCH 079/579] Fix collection popover --- osu.Game/Screens/Ranking/CollectionPopover.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/CollectionPopover.cs b/osu.Game/Screens/Ranking/CollectionPopover.cs index 98a5de597e51..2411ab99d8e3 100644 --- a/osu.Game/Screens/Ranking/CollectionPopover.cs +++ b/osu.Game/Screens/Ranking/CollectionPopover.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 osu.Framework.Allocation; using osu.Framework.Graphics; @@ -23,7 +21,7 @@ public partial class CollectionPopover : OsuPopover private RealmAccess realm { get; set; } = null!; [Resolved] - private ManageCollectionsDialog manageCollectionsDialog { get; set; } + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } public CollectionPopover(BeatmapInfo beatmapInfo) : base(false) From 6a4872faa8323f9bf3abcc059f2895ea430963c7 Mon Sep 17 00:00:00 2001 From: Layendan Date: Sun, 21 Jul 2024 23:46:04 -0700 Subject: [PATCH 080/579] Remove nullable disable --- .../Visual/Ranking/TestSceneCollectionButton.cs | 8 +++----- .../Visual/Ranking/TestSceneFavouriteButton.cs | 6 ++---- osu.Game/Screens/Ranking/CollectionButton.cs | 2 -- osu.Game/Screens/Ranking/FavouriteButton.cs | 16 +++++++--------- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs index 2cd75f6cef60..4449aae2575f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.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.Framework.Graphics; @@ -18,7 +16,7 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneCollectionButton : OsuManualInputManagerTestScene { - private CollectionButton collectionButton; + private CollectionButton? collectionButton; private readonly BeatmapInfo beatmapInfo = new BeatmapInfo { OnlineID = 88 }; [SetUpSteps] @@ -43,7 +41,7 @@ public void TestCollectionButton() { AddStep("click collection button", () => { - InputManager.MoveMouseTo(collectionButton); + InputManager.MoveMouseTo(collectionButton!); InputManager.Click(MouseButton.Left); }); @@ -59,7 +57,7 @@ public void TestCollectionButton() AddStep("click collection button", () => { - InputManager.MoveMouseTo(collectionButton); + InputManager.MoveMouseTo(collectionButton!); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs index b281fc1bbf08..a90fbc0c8412 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.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 NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; @@ -17,7 +15,7 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneFavouriteButton : OsuTestScene { - private FavouriteButton favourite; + private FavouriteButton? favourite; private readonly BeatmapSetInfo beatmapSetInfo = new BeatmapSetInfo { OnlineID = 88 }; private readonly BeatmapSetInfo invalidBeatmapSetInfo = new BeatmapSetInfo(); @@ -83,7 +81,7 @@ public void TestInvalidBeatmap() private void checkEnabled(bool expected) { - AddAssert("is " + (expected ? "enabled" : "disabled"), () => favourite.Enabled.Value == expected); + AddAssert("is " + (expected ? "enabled" : "disabled"), () => favourite!.Enabled.Value == expected); } } } diff --git a/osu.Game/Screens/Ranking/CollectionButton.cs b/osu.Game/Screens/Ranking/CollectionButton.cs index 99a51e03d963..a3e2864c7e52 100644 --- a/osu.Game/Screens/Ranking/CollectionButton.cs +++ b/osu.Game/Screens/Ranking/CollectionButton.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 osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index 95e1fdf98540..caa0eddb5500 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -27,19 +25,19 @@ public partial class FavouriteButton : OsuAnimatedButton private readonly SpriteIcon icon; public readonly BeatmapSetInfo BeatmapSetInfo; - private APIBeatmapSet beatmapSet; + private APIBeatmapSet? beatmapSet; private readonly Bindable current; - private PostBeatmapFavouriteRequest favouriteRequest; + private PostBeatmapFavouriteRequest? favouriteRequest; private readonly LoadingLayer loading; private readonly IBindable localUser = new Bindable(); [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; public FavouriteButton(BeatmapSetInfo beatmapSetInfo) { @@ -102,6 +100,9 @@ private void getBeatmapSet() private void toggleFavouriteStatus() { + if (beatmapSet == null) + return; + Enabled.Value = false; loading.Show(); @@ -144,9 +145,6 @@ private void updateUser() private void updateState() { - if (current?.Value == null) - return; - if (current.Value.Favourited) { background.Colour = colours.Green; From 9fb9a54a4d611eb4f23e47f02668d962f4a22d43 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 22 Jul 2024 11:34:07 +0200 Subject: [PATCH 081/579] hold shift to adjust velocity instead of duration --- .../Sliders/SliderSelectionBlueprint.cs | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 2f73dedc6437..339ca55cb290 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -227,10 +227,19 @@ protected override bool OnMouseDown(MouseDownEvent e) } private Vector2 lengthAdjustMouseOffset; + private double oldDuration; + private double oldVelocity; + private double desiredDistance; + private bool isAdjustingLength; + private bool adjustVelocityMomentary; private void startAdjustingLength(DragStartEvent e) { + isAdjustingLength = true; + adjustVelocityMomentary = e.ShiftPressed; lengthAdjustMouseOffset = ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position - HitObject.Path.PositionAt(1); + oldDuration = HitObject.Path.Distance / HitObject.SliderVelocityMultiplier; + oldVelocity = HitObject.SliderVelocityMultiplier; changeHandler?.BeginChange(); } @@ -238,22 +247,27 @@ private void endAdjustLength() { trimExcessControlPoints(HitObject.Path); changeHandler?.EndChange(); + isAdjustingLength = false; } - private void adjustLength(MouseEvent e) + private void adjustLength(MouseEvent e) => adjustLength(findClosestPathDistance(e), e.ShiftPressed); + + private void adjustLength(double proposedDistance, bool adjustVelocity) { - double oldDistance = HitObject.Path.Distance; - double proposedDistance = findClosestPathDistance(e); + desiredDistance = proposedDistance; + proposedDistance = MathHelper.Clamp(proposedDistance, 1, HitObject.Path.CalculatedDistance); + double proposedVelocity = oldVelocity; - proposedDistance = MathHelper.Clamp(proposedDistance, 0, HitObject.Path.CalculatedDistance); - proposedDistance = MathHelper.Clamp(proposedDistance, - 0.1 * oldDistance / HitObject.SliderVelocityMultiplier, - 10 * oldDistance / HitObject.SliderVelocityMultiplier); + if (adjustVelocity) + { + proposedDistance = MathHelper.Clamp(proposedDistance, 0.1 * oldDuration, 10 * oldDuration); + proposedVelocity = proposedDistance / oldDuration; + } - if (Precision.AlmostEquals(proposedDistance, oldDistance)) + if (Precision.AlmostEquals(proposedDistance, HitObject.Path.Distance) && Precision.AlmostEquals(proposedVelocity, HitObject.SliderVelocityMultiplier)) return; - HitObject.SliderVelocityMultiplier *= proposedDistance / oldDistance; + HitObject.SliderVelocityMultiplier = proposedVelocity; HitObject.Path.ExpectedDistance.Value = proposedDistance; editorBeatmap?.Update(HitObject); } @@ -374,9 +388,24 @@ protected override bool OnKeyDown(KeyDownEvent e) return true; } + if (isAdjustingLength && e.ShiftPressed != adjustVelocityMomentary) + { + adjustVelocityMomentary = e.ShiftPressed; + adjustLength(desiredDistance, adjustVelocityMomentary); + return true; + } + return false; } + protected override void OnKeyUp(KeyUpEvent e) + { + if (!IsSelected || !isAdjustingLength || e.ShiftPressed == adjustVelocityMomentary) return; + + adjustVelocityMomentary = e.ShiftPressed; + adjustLength(desiredDistance, adjustVelocityMomentary); + } + private PathControlPoint addControlPoint(Vector2 position) { position -= HitObject.Position; From c57232c2201f9e989cebd918d3fbf65745e06b01 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 22 Jul 2024 11:58:53 +0200 Subject: [PATCH 082/579] enforce minimum duration based on snap --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 339ca55cb290..785febab4be9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -228,7 +228,7 @@ protected override bool OnMouseDown(MouseDownEvent e) private Vector2 lengthAdjustMouseOffset; private double oldDuration; - private double oldVelocity; + private double oldVelocityMultiplier; private double desiredDistance; private bool isAdjustingLength; private bool adjustVelocityMomentary; @@ -239,7 +239,7 @@ private void startAdjustingLength(DragStartEvent e) adjustVelocityMomentary = e.ShiftPressed; lengthAdjustMouseOffset = ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position - HitObject.Path.PositionAt(1); oldDuration = HitObject.Path.Distance / HitObject.SliderVelocityMultiplier; - oldVelocity = HitObject.SliderVelocityMultiplier; + oldVelocityMultiplier = HitObject.SliderVelocityMultiplier; changeHandler?.BeginChange(); } @@ -255,13 +255,17 @@ private void endAdjustLength() private void adjustLength(double proposedDistance, bool adjustVelocity) { desiredDistance = proposedDistance; - proposedDistance = MathHelper.Clamp(proposedDistance, 1, HitObject.Path.CalculatedDistance); - double proposedVelocity = oldVelocity; + double proposedVelocity = oldVelocityMultiplier; if (adjustVelocity) { - proposedDistance = MathHelper.Clamp(proposedDistance, 0.1 * oldDuration, 10 * oldDuration); proposedVelocity = proposedDistance / oldDuration; + proposedDistance = MathHelper.Clamp(proposedDistance, 0.1 * oldDuration, 10 * oldDuration); + } + else + { + double minDistance = distanceSnapProvider?.GetBeatSnapDistanceAt(HitObject, false) * oldVelocityMultiplier ?? 1; + proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance); } if (Precision.AlmostEquals(proposedDistance, HitObject.Path.Distance) && Precision.AlmostEquals(proposedVelocity, HitObject.SliderVelocityMultiplier)) From ad1a86ebdcfc8f05380331c2aae3c0f46b084496 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 19:05:14 +0800 Subject: [PATCH 083/579] Implement the overlay --- osu.Game/Skinning/LegacyKeyCounter.cs | 101 +++++++++++++++++++ osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 79 +++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 20 ++-- 3 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Skinning/LegacyKeyCounter.cs create mode 100644 osu.Game/Skinning/LegacyKeyCounterDisplay.cs diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs new file mode 100644 index 000000000000..73534a8e5157 --- /dev/null +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Skinning +{ + public partial class LegacyKeyCounter : KeyCounter + { + public bool UsesFixedAnchor { get; set; } + + public float TransitionDuration { get; set; } = 150f; + + public Colour4 KeyTextColour { get; set; } = Colour4.White; + + public Colour4 KeyDownBackgroundColour { get; set; } = Colour4.Yellow; + + public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; + + private Container keyContainer = null!; + + private SkinnableSprite overlayKey = null!; + + private OsuSpriteText overlayKeyText = null!; + + public LegacyKeyCounter(InputTrigger trigger) + : base(trigger) + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + Child = keyContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + overlayKey = new SkinnableSprite + { + Blending = Multiplicative, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BypassAutoSizeAxes = Axes.Both, + SpriteName = { Value = "inputoverlay-key" }, + Rotation = -90, + }, + overlayKeyText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = trigger.Name, + Colour = KeyTextColour, + Font = OsuFont.Default.With(fixedWidth: true), + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Width = Math.Max(overlayKey.Width, 43 * 1.05f); + Height = Math.Max(overlayKey.Height, 43 * 1.05f); + } + + protected override void Activate(bool forwardPlayback = true) + { + base.Activate(forwardPlayback); + keyContainer.ScaleTo(0.75f, TransitionDuration); + keyContainer.FadeColour(KeyDownBackgroundColour, TransitionDuration); + overlayKeyText.Text = CountPresses.Value.ToString(); + } + + protected override void Deactivate(bool forwardPlayback = true) + { + base.Deactivate(forwardPlayback); + keyContainer.ScaleTo(1f, TransitionDuration); + keyContainer.FadeColour(KeyUpBackgroundColour, TransitionDuration); + } + + public static BlendingParameters Multiplicative + { + get + { + BlendingParameters result = default(BlendingParameters); + result.Source = BlendingType.SrcAlpha; + result.Destination = BlendingType.OneMinusSrcAlpha; + result.SourceAlpha = BlendingType.One; + result.DestinationAlpha = BlendingType.One; + result.RGBEquation = BlendingEquation.Add; + result.AlphaEquation = BlendingEquation.Add; + return result; + } + } + } +} diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs new file mode 100644 index 000000000000..ace582af5cf4 --- /dev/null +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Play.HUD; +using osuTK.Graphics; +using osu.Framework.Allocation; + +namespace osu.Game.Skinning +{ + public partial class LegacyKeyCounterDisplay : KeyCounterDisplay + { + private const float key_transition_time = 50; + + protected override FillFlowContainer KeyFlow { get; } = null!; + + private SkinnableSprite overlayBackground = null!; + + public LegacyKeyCounterDisplay() + { + AutoSizeAxes = Axes.Both; + + AddRangeInternal(new Drawable[] + { + overlayBackground = new SkinnableSprite + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + //BypassAutoSizeAxes = Axes.Both, + SpriteName = { Value= "inputoverlay-background" }, + }, + KeyFlow = new FillFlowContainer + { + Padding = new MarginPadding + { + Horizontal = 7f * 1.05f, + }, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }, + }); + } + + [BackgroundDependencyLoader] + private void load(ISkinSource source) + { + source.GetConfig("InputOverlayText")?.BindValueChanged(v => + { + KeyTextColor = v.NewValue; + }, true); + } + + protected override KeyCounter CreateCounter(InputTrigger trigger) => new LegacyKeyCounter(trigger) + { + TransitionDuration = key_transition_time, + KeyTextColour = keyTextColor, + }; + + private Color4 keyTextColor = Color4.White; + + public Color4 KeyTextColor + { + get => keyTextColor; + set + { + if (value != keyTextColor) + { + keyTextColor = value; + foreach (var child in KeyFlow.Cast()) + child.KeyTextColour = value; + } + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 816cfc0a2d11..a890c5ffabeb 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -381,22 +382,22 @@ protected override void ParseConfigurationStream(Stream stream) } var hitError = container.OfType().FirstOrDefault(); - var keyCounter = container.OfType().FirstOrDefault(); if (hitError != null) { hitError.Anchor = Anchor.BottomCentre; hitError.Origin = Anchor.CentreLeft; hitError.Rotation = -90; + } - if (keyCounter != null) - { - const float padding = 10; + var keyCounter = container.OfType().FirstOrDefault(); - keyCounter.Anchor = Anchor.BottomRight; - keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(-padding, -(padding + hitError.Width)); - } + if (keyCounter != null) + { + keyCounter.Rotation = 90f; + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.TopCentre; + keyCounter.Position = new Vector2(0); } }) { @@ -408,7 +409,8 @@ protected override void ParseConfigurationStream(Stream stream) new LegacySongProgress(), new LegacyHealthDisplay(), new BarHitErrorMeter(), - new DefaultKeyCounterDisplay() + //new DefaultKeyCounterDisplay(), + new LegacyKeyCounterDisplay(), } }; } From 777a0deb0fcc814b0b4f59a1f2a67011462d0556 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 19:45:53 +0800 Subject: [PATCH 084/579] Update the offset formula --- osu.Game/Skinning/LegacyKeyCounter.cs | 4 ++-- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 73534a8e5157..c3125c3eda2c 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -64,8 +64,8 @@ public LegacyKeyCounter(InputTrigger trigger) protected override void LoadComplete() { base.LoadComplete(); - Width = Math.Max(overlayKey.Width, 43 * 1.05f); - Height = Math.Max(overlayKey.Height, 43 * 1.05f); + Width = Math.Max(overlayKey.Width, 48 * 0.95f); + Height = Math.Max(overlayKey.Height, 48 * 0.95f); } protected override void Activate(bool forwardPlayback = true) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index ace582af5cf4..e395aefe8abf 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -28,15 +28,14 @@ public LegacyKeyCounterDisplay() { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - //BypassAutoSizeAxes = Axes.Both, SpriteName = { Value= "inputoverlay-background" }, }, KeyFlow = new FillFlowContainer { - Padding = new MarginPadding - { - Horizontal = 7f * 1.05f, - }, + // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay + // 24px away from the container, there're 4 counter in legacy, so divide by 4 + // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate + X = (24 / 4) * 1.05f, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, Direction = FillDirection.Horizontal, From 5dcc8b7a8f403dcfa6e2f1e6cfba409224cf844c Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 19:56:43 +0800 Subject: [PATCH 085/579] Make the text are always horizontal --- osu.Game/Skinning/LegacyKeyCounter.cs | 14 +++++++++++++- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index c3125c3eda2c..bb1532d75d17 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -20,6 +20,17 @@ public partial class LegacyKeyCounter : KeyCounter public Colour4 KeyDownBackgroundColour { get; set; } = Colour4.Yellow; + private float keyTextRotation = 0f; + public float KeyTextRotation + { + get => keyTextRotation; + set + { + keyTextRotation = value; + overlayKeyText.Rotation = value; + } + } + public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; private Container keyContainer = null!; @@ -56,6 +67,7 @@ public LegacyKeyCounter(InputTrigger trigger) Text = trigger.Name, Colour = KeyTextColour, Font = OsuFont.Default.With(fixedWidth: true), + Rotation = KeyTextRotation }, } }; @@ -65,7 +77,7 @@ protected override void LoadComplete() { base.LoadComplete(); Width = Math.Max(overlayKey.Width, 48 * 0.95f); - Height = Math.Max(overlayKey.Height, 48 * 0.95f); + Height = Math.Max(overlayKey.Height, 46 * 0.95f); } protected override void Activate(bool forwardPlayback = true) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index e395aefe8abf..1847effb3bcf 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -57,8 +57,17 @@ private void load(ISkinSource source) { TransitionDuration = key_transition_time, KeyTextColour = keyTextColor, + KeyTextRotation = -Rotation, }; + protected override void Update() + { + base.Update(); + // keep the text are always horizontal + foreach (var child in KeyFlow.Cast()) + child.KeyTextRotation = -Rotation; + } + private Color4 keyTextColor = Color4.White; public Color4 KeyTextColor From f7dc0b65dacd8e98733eb87873f70adb97f2bf56 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 20:47:49 +0800 Subject: [PATCH 086/579] Clean up the code --- osu.Game/Skinning/LegacyKeyCounter.cs | 13 ++++--------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 4 +--- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index bb1532d75d17..333e120024ab 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -20,6 +19,8 @@ public partial class LegacyKeyCounter : KeyCounter public Colour4 KeyDownBackgroundColour { get; set; } = Colour4.Yellow; + public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; + private float keyTextRotation = 0f; public float KeyTextRotation { @@ -31,8 +32,6 @@ public float KeyTextRotation } } - public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; - private Container keyContainer = null!; private SkinnableSprite overlayKey = null!; @@ -71,13 +70,9 @@ public LegacyKeyCounter(InputTrigger trigger) }, } }; - } - protected override void LoadComplete() - { - base.LoadComplete(); - Width = Math.Max(overlayKey.Width, 48 * 0.95f); - Height = Math.Max(overlayKey.Height, 46 * 0.95f); + // Legacy key counter size + Height = Width = 48 * 0.95f; } protected override void Activate(bool forwardPlayback = true) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 1847effb3bcf..091691657b3a 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -16,15 +16,13 @@ public partial class LegacyKeyCounterDisplay : KeyCounterDisplay protected override FillFlowContainer KeyFlow { get; } = null!; - private SkinnableSprite overlayBackground = null!; - public LegacyKeyCounterDisplay() { AutoSizeAxes = Axes.Both; AddRangeInternal(new Drawable[] { - overlayBackground = new SkinnableSprite + new SkinnableSprite { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, From dce894108ab0679106b940b46a7cd40dd166674b Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 20:50:08 +0800 Subject: [PATCH 087/579] Remove unused blending mode --- osu.Game/Skinning/LegacyKeyCounter.cs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 333e120024ab..ce757e25e1db 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -34,8 +34,6 @@ public float KeyTextRotation private Container keyContainer = null!; - private SkinnableSprite overlayKey = null!; - private OsuSpriteText overlayKeyText = null!; public LegacyKeyCounter(InputTrigger trigger) @@ -50,9 +48,8 @@ public LegacyKeyCounter(InputTrigger trigger) Anchor = Anchor.Centre, Children = new Drawable[] { - overlayKey = new SkinnableSprite + new SkinnableSprite { - Blending = Multiplicative, Anchor = Anchor.Centre, Origin = Anchor.Centre, BypassAutoSizeAxes = Axes.Both, @@ -89,20 +86,5 @@ protected override void Deactivate(bool forwardPlayback = true) keyContainer.ScaleTo(1f, TransitionDuration); keyContainer.FadeColour(KeyUpBackgroundColour, TransitionDuration); } - - public static BlendingParameters Multiplicative - { - get - { - BlendingParameters result = default(BlendingParameters); - result.Source = BlendingType.SrcAlpha; - result.Destination = BlendingType.OneMinusSrcAlpha; - result.SourceAlpha = BlendingType.One; - result.DestinationAlpha = BlendingType.One; - result.RGBEquation = BlendingEquation.Add; - result.AlphaEquation = BlendingEquation.Add; - return result; - } - } } } From a015fde014aebee865a0ab57736ab340e3fceea9 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 20:53:06 +0800 Subject: [PATCH 088/579] Change the default height to match the stable --- osu.Game/Skinning/LegacySkin.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a890c5ffabeb..281a9c605333 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -395,9 +395,9 @@ protected override void ParseConfigurationStream(Stream stream) if (keyCounter != null) { keyCounter.Rotation = 90f; - keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.TopCentre; - keyCounter.Position = new Vector2(0); + keyCounter.Position = new Vector2(0, -340); } }) { @@ -409,7 +409,6 @@ protected override void ParseConfigurationStream(Stream stream) new LegacySongProgress(), new LegacyHealthDisplay(), new BarHitErrorMeter(), - //new DefaultKeyCounterDisplay(), new LegacyKeyCounterDisplay(), } }; From 9fe369b7f41952dba6b9ddb779f9d792d5e2a867 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 21:08:08 +0800 Subject: [PATCH 089/579] Replace `SkinnableSprite` with `Sprite` --- osu.Game/Skinning/LegacyKeyCounter.cs | 18 ++++++++++++++++-- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 12 ++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index ce757e25e1db..08b03b5550ba 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -22,6 +25,7 @@ public partial class LegacyKeyCounter : KeyCounter public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; private float keyTextRotation = 0f; + public float KeyTextRotation { get => keyTextRotation; @@ -36,6 +40,8 @@ public float KeyTextRotation private OsuSpriteText overlayKeyText = null!; + private Sprite keySprite = null!; + public LegacyKeyCounter(InputTrigger trigger) : base(trigger) { @@ -48,12 +54,11 @@ public LegacyKeyCounter(InputTrigger trigger) Anchor = Anchor.Centre, Children = new Drawable[] { - new SkinnableSprite + keySprite = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, BypassAutoSizeAxes = Axes.Both, - SpriteName = { Value = "inputoverlay-key" }, Rotation = -90, }, overlayKeyText = new OsuSpriteText @@ -72,6 +77,15 @@ public LegacyKeyCounter(InputTrigger trigger) Height = Width = 48 * 0.95f; } + [BackgroundDependencyLoader] + private void load(ISkinSource source) + { + Texture? keyTexture = source.GetTexture($"inputoverlay-key"); + + if (keyTexture != null) + keySprite.Texture = keyTexture; + } + protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 091691657b3a..d26228c1d67f 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -7,6 +7,8 @@ using osu.Game.Screens.Play.HUD; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { @@ -16,17 +18,18 @@ public partial class LegacyKeyCounterDisplay : KeyCounterDisplay protected override FillFlowContainer KeyFlow { get; } = null!; + private Sprite backgroundSprite = null!; + public LegacyKeyCounterDisplay() { AutoSizeAxes = Axes.Both; AddRangeInternal(new Drawable[] { - new SkinnableSprite + backgroundSprite = new Sprite { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - SpriteName = { Value= "inputoverlay-background" }, }, KeyFlow = new FillFlowContainer { @@ -49,6 +52,11 @@ private void load(ISkinSource source) { KeyTextColor = v.NewValue; }, true); + + Texture? backgroundTexture = source.GetTexture($"inputoverlay-background"); + + if (backgroundTexture != null) + backgroundSprite.Texture = backgroundTexture; } protected override KeyCounter CreateCounter(InputTrigger trigger) => new LegacyKeyCounter(trigger) From 989ac56cbbee2c75231aa95c743d3acaf0442f42 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 21:12:55 +0800 Subject: [PATCH 090/579] Fix the return button being squshed --- osu.Game/Skinning/LegacySkin.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 281a9c605333..6e447242da51 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -395,9 +395,11 @@ protected override void ParseConfigurationStream(Stream stream) if (keyCounter != null) { keyCounter.Rotation = 90f; - keyCounter.Anchor = Anchor.BottomRight; + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.TopRight; keyCounter.Origin = Anchor.TopCentre; - keyCounter.Position = new Vector2(0, -340); + keyCounter.X = 0; + keyCounter.Y = container.ToLocalSpace(container.ScreenSpaceDrawQuad.BottomRight).Y - 340; } }) { From c7b110a471f94bb30b4d697124ce8cabdaf90b1e Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 22:11:28 +0800 Subject: [PATCH 091/579] * Fix the default position * Make the font match stable style --- osu.Game/Skinning/LegacyKeyCounter.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 08b03b5550ba..5640e14dbfc2 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -67,7 +67,7 @@ public LegacyKeyCounter(InputTrigger trigger) Origin = Anchor.Centre, Text = trigger.Name, Colour = KeyTextColour, - Font = OsuFont.Default.With(fixedWidth: true), + Font = OsuFont.GetFont(size: 20), Rotation = KeyTextRotation }, } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 6e447242da51..cfa7eb7872a5 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; -using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -396,10 +395,11 @@ protected override void ParseConfigurationStream(Stream stream) { keyCounter.Rotation = 90f; // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.TopRight; + keyCounter.Anchor = Anchor.CentreRight; keyCounter.Origin = Anchor.TopCentre; keyCounter.X = 0; - keyCounter.Y = container.ToLocalSpace(container.ScreenSpaceDrawQuad.BottomRight).Y - 340; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; } }) { From c52a993607d50363acabc85b0fcc275a091265f5 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 23:35:25 +0800 Subject: [PATCH 092/579] Support custom input overlay color --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 27 +++++++++++++++----- osu.Game/Skinning/LegacyKeyCounter.cs | 22 +++++++++------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 15 ++++++----- osu.Game/Skinning/LegacySkin.cs | 4 ++- osu.Game/Skinning/SkinConfiguration.cs | 4 +++ 5 files changed, 49 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 93af9cf41c5e..331b84cbf258 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats @@ -93,14 +94,8 @@ protected string StripComments(string line) return line; } - protected void HandleColours(TModel output, string line, bool allowAlpha) + private Color4 convertSettingStringToColor4(string[] split, bool allowAlpha, KeyValuePair pair) { - var pair = SplitKeyVal(line); - - bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal); - - string[] split = pair.Value.Split(','); - if (split.Length != 3 && split.Length != 4) throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}"); @@ -115,6 +110,17 @@ protected void HandleColours(TModel output, string line, bool allowAlpha { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); } + return colour; + } + + protected void HandleColours(TModel output, string line, bool allowAlpha) + { + var pair = SplitKeyVal(line); + + string[] split = pair.Value.Split(','); + Color4 colour = convertSettingStringToColor4(split, allowAlpha, pair); + + bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal); if (isCombo) { @@ -128,6 +134,13 @@ protected void HandleColours(TModel output, string line, bool allowAlpha tHasCustomColours.CustomColours[pair.Key] = colour; } + bool isInputOverlayText = pair.Key.StartsWith(@"InputOverlayText"); + + if (isInputOverlayText) + { + if (!(output is SkinConfiguration tSkinConfiguration)) return; + tSkinConfiguration.InputOverlayText = colour; + } } protected KeyValuePair SplitKeyVal(string line, char separator = ':', bool shouldTrim = true) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 5640e14dbfc2..85d5a897fb39 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -16,13 +16,19 @@ public partial class LegacyKeyCounter : KeyCounter { public bool UsesFixedAnchor { get; set; } - public float TransitionDuration { get; set; } = 150f; + public float TransitionDuration { get; set; } = 50f; - public Colour4 KeyTextColour { get; set; } = Colour4.White; - - public Colour4 KeyDownBackgroundColour { get; set; } = Colour4.Yellow; + public Colour4 KeyTextColour + { + get => keyTextColour; + set + { + keyTextColour = value; + overlayKeyText.Colour = value; + } + } - public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; + private Colour4 keyTextColour = Colour4.White; private float keyTextRotation = 0f; @@ -66,7 +72,7 @@ public LegacyKeyCounter(InputTrigger trigger) Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = trigger.Name, - Colour = KeyTextColour, + Colour = keyTextColour, Font = OsuFont.GetFont(size: 20), Rotation = KeyTextRotation }, @@ -89,8 +95,7 @@ private void load(ISkinSource source) protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); - keyContainer.ScaleTo(0.75f, TransitionDuration); - keyContainer.FadeColour(KeyDownBackgroundColour, TransitionDuration); + keyContainer.ScaleTo(0.8f, TransitionDuration); overlayKeyText.Text = CountPresses.Value.ToString(); } @@ -98,7 +103,6 @@ protected override void Deactivate(bool forwardPlayback = true) { base.Deactivate(forwardPlayback); keyContainer.ScaleTo(1f, TransitionDuration); - keyContainer.FadeColour(KeyUpBackgroundColour, TransitionDuration); } } } diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index d26228c1d67f..a585d93c5ada 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -45,10 +44,13 @@ public LegacyKeyCounterDisplay() }); } - [BackgroundDependencyLoader] - private void load(ISkinSource source) + [Resolved] + private ISkinSource source { get; set; } = null!; + + protected override void LoadComplete() { - source.GetConfig("InputOverlayText")?.BindValueChanged(v => + base.LoadComplete(); + source.GetConfig(SkinConfiguration.LegacySetting.InputOverlayText)?.BindValueChanged(v => { KeyTextColor = v.NewValue; }, true); @@ -69,14 +71,15 @@ private void load(ISkinSource source) protected override void Update() { base.Update(); + // keep the text are always horizontal foreach (var child in KeyFlow.Cast()) child.KeyTextRotation = -Rotation; } - private Color4 keyTextColor = Color4.White; + private Colour4 keyTextColor = Colour4.White; - public Color4 KeyTextColor + public Colour4 KeyTextColor { get => keyTextColor; set diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index cfa7eb7872a5..fa8383721440 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -309,7 +310,8 @@ protected override void ParseConfigurationStream(Stream stream) { case SkinConfiguration.LegacySetting.Version: return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION)); - + case SkinConfiguration.LegacySetting.InputOverlayText: + return SkinUtils.As(new Bindable(Configuration.InputOverlayText ?? Colour4.White)); default: return genericLookup(legacySetting); } diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 937cca0aebee..abfcaff1d8bf 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Graphics; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -38,8 +39,11 @@ public enum LegacySetting AnimationFramerate, LayeredHitSounds, AllowSliderBallTint, + InputOverlayText, } + public Colour4? InputOverlayText { get; internal set; } + public static List DefaultComboColours { get; } = new List { new Color4(255, 192, 0, 255), From 661f58a39747ed03ec94bd4df12168ac055d5e2f Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:18:05 +0800 Subject: [PATCH 093/579] Add test coverage --- .../Archives/modified-classic-20240724.osk | Bin 0 -> 518 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk new file mode 100644 index 0000000000000000000000000000000000000000..ed8f3fd3fdd58672e43d31fdf7ab9b0f48e4cac9 GIT binary patch literal 518 zcmWIWW@Zs#U|`^2_?h@4!u!p6tu;X2aj*yjLveOyo?d2NrfSc|yh8>euI1kU+M}1b ztP)zl9JI|ZXKs|(m4|}+8(RO$ZIX5UX~t&P8J`ibU3u=}&wK1|iQN}Jwm|X4lczm# zOE-x4OmR%U=yCMB$RVDhK3P9wlm7gI*%ul3l?a!!L@9MX{Vt)GOx9F<3Ri6VFcfI=a;HK`5m1}>>oOTUvm*3gtmvpu< z_?wpOwzKDE*$1Hbs4sTW-zK1MW&trT5QBV_nU|KYmsOmfxBA)Bpn!l+-e-J6Hw6Wp z($@$z^pmlLBigjx*dKw zectr>)9KF(gyvNqRqd=)o!O~6^JnF}%FLeui@)A!y!L`MJ^k6!)Mray?_9D>b=tEj zKb9<0R|{`)DHTcRno_Ydi!s2Pkx7IBcLV@k3;~TG3Kkvcy3jod)x*HhcnPWt?&Sb) RRyL3{6A%^x>3T*G4*(EG#>4;s literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index f2547b4f5d15..039e85bbce58 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -65,7 +65,9 @@ public class SkinDeserialisationTest // Covers default rank display "Archives/modified-default-20230809.osk", // Covers legacy rank display - "Archives/modified-classic-20230809.osk" + "Archives/modified-classic-20230809.osk", + // Covcers legacy key counter + "Archives/modified-classic-20240724.osk" }; /// From 95f287104eb85c0165727bcd44e65cf841b6d006 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:24:58 +0800 Subject: [PATCH 094/579] Add visual test seane --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 2d2b6c3bed4d..57bfb5fddf84 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -9,6 +9,7 @@ using osu.Framework.Utils; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; using osuTK; using osuTK.Input; @@ -56,6 +57,11 @@ public TestSceneKeyCounter() Anchor = Anchor.Centre, Scale = new Vector2(1, -1) }, + new LegacyKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }, new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -89,6 +95,12 @@ public TestSceneKeyCounter() Anchor = Anchor.Centre, Rotation = 90, }, + new LegacyKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Rotation = 90, + }, } }, } From 395f8424b5aed3837a5aba63d6cb79eb426c3d81 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:30:08 +0800 Subject: [PATCH 095/579] Match the stable animation --- osu.Game/Skinning/LegacyKeyCounter.cs | 2 +- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 85d5a897fb39..0637118bd1f7 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -95,7 +95,7 @@ private void load(ISkinSource source) protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); - keyContainer.ScaleTo(0.8f, TransitionDuration); + keyContainer.ScaleTo(0.75f, TransitionDuration, Easing.OutQuad); overlayKeyText.Text = CountPresses.Value.ToString(); } diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index a585d93c5ada..adc5d8797318 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning { public partial class LegacyKeyCounterDisplay : KeyCounterDisplay { - private const float key_transition_time = 50; + private const float key_transition_time = 100; protected override FillFlowContainer KeyFlow { get; } = null!; From e2beacb3ddd5e83ab7f7bb2487d13f548f9f5252 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:31:01 +0800 Subject: [PATCH 096/579] Remove logging --- osu.Game/Skinning/LegacySkin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fa8383721440..f754fee0775b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; -using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; From 56143de2c6ade3e8ffe705bc4dedc6df4acfcb00 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:39:36 +0800 Subject: [PATCH 097/579] Update offset factor --- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index adc5d8797318..ccb1c2140215 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK; namespace osu.Game.Skinning { @@ -35,7 +36,7 @@ public LegacyKeyCounterDisplay() // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay // 24px away from the container, there're 4 counter in legacy, so divide by 4 // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate - X = (24 / 4) * 1.05f, + X = (24 / 4) * 1f, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, Direction = FillDirection.Horizontal, From b24be96d047d02a258a1ea40569efdd65fe4dae6 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:57:30 +0800 Subject: [PATCH 098/579] Fix code quality --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 5 ++- osu.Game/Skinning/LegacyKeyCounter.cs | 10 ++--- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 39 ++++++++++---------- osu.Game/Skinning/LegacySkin.cs | 2 + 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 331b84cbf258..4f116663927f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -110,6 +110,7 @@ private Color4 convertSettingStringToColor4(string[] split, bool allowAlpha, Key { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); } + return colour; } @@ -134,11 +135,13 @@ protected void HandleColours(TModel output, string line, bool allowAlpha tHasCustomColours.CustomColours[pair.Key] = colour; } - bool isInputOverlayText = pair.Key.StartsWith(@"InputOverlayText"); + + bool isInputOverlayText = pair.Key == @"InputOverlayText"; if (isInputOverlayText) { if (!(output is SkinConfiguration tSkinConfiguration)) return; + tSkinConfiguration.InputOverlayText = colour; } } diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 0637118bd1f7..88ca86c63be4 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -30,7 +30,7 @@ public Colour4 KeyTextColour private Colour4 keyTextColour = Colour4.White; - private float keyTextRotation = 0f; + private float keyTextRotation; public float KeyTextRotation { @@ -42,11 +42,11 @@ public float KeyTextRotation } } - private Container keyContainer = null!; + private readonly Container keyContainer; - private OsuSpriteText overlayKeyText = null!; + private readonly OsuSpriteText overlayKeyText; - private Sprite keySprite = null!; + private readonly Sprite keySprite; public LegacyKeyCounter(InputTrigger trigger) : base(trigger) @@ -86,7 +86,7 @@ public LegacyKeyCounter(InputTrigger trigger) [BackgroundDependencyLoader] private void load(ISkinSource source) { - Texture? keyTexture = source.GetTexture($"inputoverlay-key"); + Texture? keyTexture = source.GetTexture(@"inputoverlay-key"); if (keyTexture != null) keySprite.Texture = keyTexture; diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index ccb1c2140215..651afb788a92 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osuTK; namespace osu.Game.Skinning { @@ -16,9 +15,9 @@ public partial class LegacyKeyCounterDisplay : KeyCounterDisplay { private const float key_transition_time = 100; - protected override FillFlowContainer KeyFlow { get; } = null!; + protected override FillFlowContainer KeyFlow { get; } - private Sprite backgroundSprite = null!; + private readonly Sprite backgroundSprite; public LegacyKeyCounterDisplay() { @@ -26,22 +25,22 @@ public LegacyKeyCounterDisplay() AddRangeInternal(new Drawable[] { - backgroundSprite = new Sprite - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }, - KeyFlow = new FillFlowContainer - { - // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay - // 24px away from the container, there're 4 counter in legacy, so divide by 4 - // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate - X = (24 / 4) * 1f, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - }, + backgroundSprite = new Sprite + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + KeyFlow = new FillFlowContainer + { + // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay + // 24px away from the container, there're 4 counter in legacy, so divide by 4 + // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate + X = 24f / 4f, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }, }); } @@ -56,7 +55,7 @@ protected override void LoadComplete() KeyTextColor = v.NewValue; }, true); - Texture? backgroundTexture = source.GetTexture($"inputoverlay-background"); + Texture? backgroundTexture = source.GetTexture(@"inputoverlay-background"); if (backgroundTexture != null) backgroundSprite.Texture = backgroundTexture; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f754fee0775b..7a3cc2d78552 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -309,8 +309,10 @@ protected override void ParseConfigurationStream(Stream stream) { case SkinConfiguration.LegacySetting.Version: return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION)); + case SkinConfiguration.LegacySetting.InputOverlayText: return SkinUtils.As(new Bindable(Configuration.InputOverlayText ?? Colour4.White)); + default: return genericLookup(legacySetting); } From fede6b3657e75391051bc2b8259194999769331a Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 13:09:21 +0800 Subject: [PATCH 099/579] Fix indent problems --- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 651afb788a92..44aab407a58d 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -28,7 +28,7 @@ public LegacyKeyCounterDisplay() backgroundSprite = new Sprite { Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, + Origin = Anchor.TopLeft, }, KeyFlow = new FillFlowContainer { From 0306ef4096aeb8aafd1eb49c8afefcd338a62f05 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 14:13:45 +0800 Subject: [PATCH 100/579] Update test assets --- .../Archives/modified-classic-20240724.osk | Bin 518 -> 1446 bytes .../Skins/SkinDeserialisationTest.cs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk index ed8f3fd3fdd58672e43d31fdf7ab9b0f48e4cac9..29a06abf1d00fae7cb73a2cfb868f96d2be2bcca 100644 GIT binary patch literal 1446 zcmWIWW@Zs#U|`^2Xes;=Ay6+@eI3YK!Uz&!U?|Sc%+t%v%e>le=*Mg*;!^Fs@32a! z(#D%-g)}dfg_MLi2rvB+`CnCRTYXCP)>DEznB5&7F^J4zi&}TU&EYufrL!6H=9$Fj zzfbMFZM#_bdCG=|jB8uYC?+ppd9}IX)Hc=UeG17p63Sba)G=K?E}Zw*F=^j{zDpKf z4(YP&pL#y2n94k~oV?J7d#%}Z8H-7`vr3JP&h}^Bvhq5)E%?ltz+^=dFJBG$-lFRg;y_OHoLh~!*rv{evXuijJUG6x=Oi_L&ocOs2HwSG2WW~n&aAAQS^2U`Z)IlZQcG34l`lI_PE`n$Wu2j<8n-Tr0o5PoiSe~TObiTy{0t0Y zK!5lqX6AW>x;W?O7Ubup=9LtKJ#;rLHvh7LNUi%Ldzacg-Q_1Yv99D_%-Y2ec#&6b znP$+Vt)I$6u0jGk!Ik@M`-cG`F~Reg3(VTjk^S7tOiGa8tIMwPuR> zs;|c?H5|B?J010jc#y?vA)R(IPABhRk?zvsPmO0AdX~p{=>>n144S6%D@$zkzEevX zbF62t*!EdApjfqfiq8CPm(RDJ6s!^|YWCSOH&BjyUQN@{)>%z&PR4YERWJYKk(rm% z^)~GKk-e^6Q&bB!GHt){IB#A4sSTy;KApN|k;tUwy<UYBLWhWtNNo>#})T#J}&#g8QGVXV~cOPIPi!DQR%bYjJm-SM=-j7$1SI zCE?elYotG_H}wm*eu(Gt^OOj=%F%p__4^*LgQp9eS?v8M$xN@WQa1Ts`-p8yS+?lE zCs&p&xw3jv{o%bF!c)GTzjn%1cwIhg^tR#!-B#=^lU{O!b}jfSc=l4^-(aWRaXZ#0 zKA61OV8Oij*DnJe`hI_4v+BNT9kcsUff)uj6P|9Xx7B#Hk5|U?{f3}-8w9h|ITTDK zBTG+yC~|1Kv&T8)2kV=I3rcuHT>SGbO;%&G2x>In(}~50+)zN#J|xF?Z$ejAhTZr5t(IpwoJ4#}P5N zjx?U-4HM0}pMEbp?bviwVcV0>^P{g-zSchXkv-zL^1k{z|^M;#C$*;kds)MmYJH9f|Q+# zG`)2EeNK6PJ>z{kd{fX0HcqD2&)wdQdtO8cY@arLwHPpEGct)V;4Ywm217t2h=LVY z=(^BzHdGG-L*rklE_gmi*NUE*5L%A{%Sh~53*8L#pg@@6&4L~<0p6@^AbA!b{0OAy J1643E008f1L+Jnj literal 518 zcmWIWW@Zs#U|`^2_?h@4!u!p6tu;X2aj*yjLveOyo?d2NrfSc|yh8>euI1kU+M}1b ztP)zl9JI|ZXKs|(m4|}+8(RO$ZIX5UX~t&P8J`ibU3u=}&wK1|iQN}Jwm|X4lczm# zOE-x4OmR%U=yCMB$RVDhK3P9wlm7gI*%ul3l?a!!L@9MX{Vt)GOx9F<3Ri6VFcfI=a;HK`5m1}>>oOTUvm*3gtmvpu< z_?wpOwzKDE*$1Hbs4sTW-zK1MW&trT5QBV_nU|KYmsOmfxBA)Bpn!l+-e-J6Hw6Wp z($@$z^pmlLBigjx*dKw zectr>)9KF(gyvNqRqd=)o!O~6^JnF}%FLeui@)A!y!L`MJ^k6!)Mray?_9D>b=tEj zKb9<0R|{`)DHTcRno_Ydi!s2Pkx7IBcLV@k3;~TG3Kkvcy3jod)x*HhcnPWt?&Sb) RRyL3{6A%^x>3T*G4*(EG#>4;s diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 039e85bbce58..534d47d617bd 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -66,7 +66,7 @@ public class SkinDeserialisationTest "Archives/modified-default-20230809.osk", // Covers legacy rank display "Archives/modified-classic-20230809.osk", - // Covcers legacy key counter + // Covers legacy key counter "Archives/modified-classic-20240724.osk" }; From ace5071d888eb644371b9255ea5f70e265a820c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 14:25:51 +0200 Subject: [PATCH 101/579] Add better test scene --- .../Gameplay/TestSceneSkinnableKeyCounter.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs new file mode 100644 index 000000000000..07c39793d222 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneSkinnableKeyCounter : SkinnableHUDComponentTestScene + { + [Cached] + private readonly InputCountController controller = new InputCountController(); + + public override void SetUpSteps() + { + AddStep("create dependencies", () => + { + Add(controller); + controller.Add(new KeyCounterKeyboardTrigger(Key.Z)); + controller.Add(new KeyCounterKeyboardTrigger(Key.X)); + controller.Add(new KeyCounterKeyboardTrigger(Key.C)); + controller.Add(new KeyCounterKeyboardTrigger(Key.V)); + + foreach (var trigger in controller.Triggers) + Add(trigger); + }); + base.SetUpSteps(); + } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + protected override Drawable CreateDefaultImplementation() => new ArgonKeyCounterDisplay(); + + protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay(); //{ Rotation = 90, }; + } +} From 087dd759be973612cd4c734cb3948ecc115465cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 14:26:16 +0200 Subject: [PATCH 102/579] Adjust layout to ballpark-match stable I dunno what the wiki is claiming with the "24px" figure or why but I'm not playing conversion games either. Dimensions ballparked via screenshots captured at x768 resolution. Also removes a weird homebrew method to keep the text upright. There is one canonical way to do this, namely `UprightAspectMaintainingContainer`. And the other key counters were already using it. --- .../Gameplay/TestSceneSkinnableKeyCounter.cs | 2 +- osu.Game/Skinning/LegacyKeyCounter.cs | 36 ++++++++----------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 28 ++++++--------- osu.Game/Skinning/LegacySkin.cs | 3 +- 4 files changed, 26 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs index 07c39793d222..098f8e3246a2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs @@ -37,6 +37,6 @@ public override void SetUpSteps() protected override Drawable CreateDefaultImplementation() => new ArgonKeyCounterDisplay(); - protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay(); //{ Rotation = 90, }; + protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay(); } } diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 88ca86c63be4..d5ba14e48460 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -30,18 +31,6 @@ public Colour4 KeyTextColour private Colour4 keyTextColour = Colour4.White; - private float keyTextRotation; - - public float KeyTextRotation - { - get => keyTextRotation; - set - { - keyTextRotation = value; - overlayKeyText.Rotation = value; - } - } - private readonly Container keyContainer; private readonly OsuSpriteText overlayKeyText; @@ -55,7 +44,7 @@ public LegacyKeyCounter(InputTrigger trigger) Anchor = Anchor.Centre; Child = keyContainer = new Container { - RelativeSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, Children = new Drawable[] @@ -64,23 +53,26 @@ public LegacyKeyCounter(InputTrigger trigger) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - BypassAutoSizeAxes = Axes.Both, - Rotation = -90, }, - overlayKeyText = new OsuSpriteText + new UprightAspectMaintainingContainer { + AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = trigger.Name, - Colour = keyTextColour, - Font = OsuFont.GetFont(size: 20), - Rotation = KeyTextRotation + Child = overlayKeyText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = trigger.Name, + Colour = keyTextColour, + Font = OsuFont.GetFont(size: 20), + }, }, } }; - // Legacy key counter size - Height = Width = 48 * 0.95f; + // matches longest dimension of default skin asset + Height = Width = 46; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 44aab407a58d..abfe607aab13 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK; namespace osu.Game.Skinning { @@ -27,18 +28,19 @@ public LegacyKeyCounterDisplay() { backgroundSprite = new Sprite { - Anchor = Anchor.TopLeft, + Anchor = Anchor.TopRight, Origin = Anchor.TopLeft, + Scale = new Vector2(1.05f, 1), + Rotation = 90, }, KeyFlow = new FillFlowContainer { - // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay - // 24px away from the container, there're 4 counter in legacy, so divide by 4 - // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate - X = 24f / 4f, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Direction = FillDirection.Horizontal, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + X = -1.5f, + Y = 7, + Spacing = new Vector2(1.8f), + Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, }, }); @@ -65,18 +67,8 @@ protected override void LoadComplete() { TransitionDuration = key_transition_time, KeyTextColour = keyTextColor, - KeyTextRotation = -Rotation, }; - protected override void Update() - { - base.Update(); - - // keep the text are always horizontal - foreach (var child in KeyFlow.Cast()) - child.KeyTextRotation = -Rotation; - } - private Colour4 keyTextColor = Colour4.White; public Colour4 KeyTextColor diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7a3cc2d78552..191ca04153c2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -396,10 +396,9 @@ protected override void ParseConfigurationStream(Stream stream) if (keyCounter != null) { - keyCounter.Rotation = 90f; // set the anchor to top right so that it won't squash to the return button to the top keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.TopCentre; + keyCounter.Origin = Anchor.CentreRight; keyCounter.X = 0; // 340px is the default height inherit from stable keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; From 3c28c116ca76e153503da8ed8465727b92ee383f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 14:49:23 +0200 Subject: [PATCH 103/579] Simplify input overlay text colour decode (and fix incorrect default) --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 10 ---------- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/SkinConfiguration.cs | 3 --- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 4f116663927f..30a78a16ed94 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO; using osu.Game.Rulesets.Objects.Legacy; -using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats @@ -135,15 +134,6 @@ protected void HandleColours(TModel output, string line, bool allowAlpha tHasCustomColours.CustomColours[pair.Key] = colour; } - - bool isInputOverlayText = pair.Key == @"InputOverlayText"; - - if (isInputOverlayText) - { - if (!(output is SkinConfiguration tSkinConfiguration)) return; - - tSkinConfiguration.InputOverlayText = colour; - } } protected KeyValuePair SplitKeyVal(string line, char separator = ':', bool shouldTrim = true) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 191ca04153c2..64965874a540 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -311,7 +311,7 @@ protected override void ParseConfigurationStream(Stream stream) return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION)); case SkinConfiguration.LegacySetting.InputOverlayText: - return SkinUtils.As(new Bindable(Configuration.InputOverlayText ?? Colour4.White)); + return SkinUtils.As(new Bindable(Configuration.CustomColours.TryGetValue(@"InputOverlayText", out var colour) ? colour : Colour4.Black)); default: return genericLookup(legacySetting); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index abfcaff1d8bf..a657a667eb8a 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Graphics; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -42,8 +41,6 @@ public enum LegacySetting InputOverlayText, } - public Colour4? InputOverlayText { get; internal set; } - public static List DefaultComboColours { get; } = new List { new Color4(255, 192, 0, 255), From 26395bd443dd5ca13f641c9486180a0d8cc74d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 15:07:15 +0200 Subject: [PATCH 104/579] Adjust animations further to match stable --- osu.Game/Skinning/LegacyKeyCounter.cs | 25 ++++++++++---------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 13 ++++++---- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index d5ba14e48460..8a182de9b78a 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -15,26 +15,24 @@ namespace osu.Game.Skinning { public partial class LegacyKeyCounter : KeyCounter { - public bool UsesFixedAnchor { get; set; } + private const float transition_duration = 160; - public float TransitionDuration { get; set; } = 50f; + public Colour4 ActiveColour { get; set; } - public Colour4 KeyTextColour + private Colour4 textColour; + + public Colour4 TextColour { - get => keyTextColour; + get => textColour; set { - keyTextColour = value; + textColour = value; overlayKeyText.Colour = value; } } - private Colour4 keyTextColour = Colour4.White; - private readonly Container keyContainer; - private readonly OsuSpriteText overlayKeyText; - private readonly Sprite keySprite; public LegacyKeyCounter(InputTrigger trigger) @@ -64,7 +62,7 @@ public LegacyKeyCounter(InputTrigger trigger) Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = trigger.Name, - Colour = keyTextColour, + Colour = textColour, Font = OsuFont.GetFont(size: 20), }, }, @@ -87,14 +85,17 @@ private void load(ISkinSource source) protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); - keyContainer.ScaleTo(0.75f, TransitionDuration, Easing.OutQuad); + keyContainer.ScaleTo(0.75f, transition_duration, Easing.Out); + keySprite.Colour = ActiveColour; overlayKeyText.Text = CountPresses.Value.ToString(); + overlayKeyText.Font = overlayKeyText.Font.With(weight: FontWeight.Bold); } protected override void Deactivate(bool forwardPlayback = true) { base.Deactivate(forwardPlayback); - keyContainer.ScaleTo(1f, TransitionDuration); + keyContainer.ScaleTo(1f, transition_duration, Easing.Out); + keySprite.Colour = Colour4.White; } } } diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index abfe607aab13..8c652085e483 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -14,7 +14,8 @@ namespace osu.Game.Skinning { public partial class LegacyKeyCounterDisplay : KeyCounterDisplay { - private const float key_transition_time = 100; + private static readonly Colour4 active_colour_top = Colour4.FromHex(@"#ffde00"); + private static readonly Colour4 active_colour_bottom = Colour4.FromHex(@"#f8009e"); protected override FillFlowContainer KeyFlow { get; } @@ -61,12 +62,16 @@ protected override void LoadComplete() if (backgroundTexture != null) backgroundSprite.Texture = backgroundTexture; + + for (int i = 0; i < KeyFlow.Count; ++i) + { + ((LegacyKeyCounter)KeyFlow[i]).ActiveColour = i < 2 ? active_colour_top : active_colour_bottom; + } } protected override KeyCounter CreateCounter(InputTrigger trigger) => new LegacyKeyCounter(trigger) { - TransitionDuration = key_transition_time, - KeyTextColour = keyTextColor, + TextColour = keyTextColor, }; private Colour4 keyTextColor = Colour4.White; @@ -80,7 +85,7 @@ public Colour4 KeyTextColor { keyTextColor = value; foreach (var child in KeyFlow.Cast()) - child.KeyTextColour = value; + child.TextColour = value; } } } From c3dae81935a18eb62ebbfa7fdc2c36af5fd9fe2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 15:41:20 +0200 Subject: [PATCH 105/579] Only add legacy key overlay to osu! and catch HUD layers --- osu.Game/Skinning/LegacySkin.cs | 140 +++++++++++++++++++------------- 1 file changed, 82 insertions(+), 58 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 64965874a540..4ca0e3cac0b1 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -356,69 +356,15 @@ protected override void ParseConfigurationStream(Stream stream) switch (lookup) { case SkinComponentsContainerLookup containerLookup: - // Only handle global level defaults for now. - if (containerLookup.Ruleset != null) - return null; switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - return new DefaultSkinComponentsContainer(container => - { - var score = container.OfType().FirstOrDefault(); - var accuracy = container.OfType().FirstOrDefault(); - - if (score != null && accuracy != null) - { - accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; - } - - var songProgress = container.OfType().FirstOrDefault(); - - if (songProgress != null && accuracy != null) - { - songProgress.Anchor = Anchor.TopRight; - songProgress.Origin = Anchor.CentreRight; - songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18; - songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); - } - - var hitError = container.OfType().FirstOrDefault(); - - if (hitError != null) - { - hitError.Anchor = Anchor.BottomCentre; - hitError.Origin = Anchor.CentreLeft; - hitError.Rotation = -90; - } - - var keyCounter = container.OfType().FirstOrDefault(); - - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - }) - { - Children = new Drawable[] - { - new LegacyComboCounter(), - new LegacyScoreCounter(), - new LegacyAccuracyCounter(), - new LegacySongProgress(), - new LegacyHealthDisplay(), - new BarHitErrorMeter(), - new LegacyKeyCounterDisplay(), - } - }; - } + return createDefaultHUDComponents(containerLookup); - return null; + default: + return null; + } case GameplaySkinComponentLookup resultComponent: @@ -442,6 +388,84 @@ protected override void ParseConfigurationStream(Stream stream) return null; } + private static DefaultSkinComponentsContainer? createDefaultHUDComponents(SkinComponentsContainerLookup containerLookup) + { + switch (containerLookup.Ruleset?.ShortName) + { + case null: + { + return new DefaultSkinComponentsContainer(container => + { + var score = container.OfType().FirstOrDefault(); + var accuracy = container.OfType().FirstOrDefault(); + + if (score != null && accuracy != null) + { + accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; + } + + var songProgress = container.OfType().FirstOrDefault(); + + if (songProgress != null && accuracy != null) + { + songProgress.Anchor = Anchor.TopRight; + songProgress.Origin = Anchor.CentreRight; + songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18; + songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); + } + + var hitError = container.OfType().FirstOrDefault(); + + if (hitError != null) + { + hitError.Anchor = Anchor.BottomCentre; + hitError.Origin = Anchor.CentreLeft; + hitError.Rotation = -90; + } + }) + { + Children = new Drawable[] + { + new LegacyComboCounter(), + new LegacyScoreCounter(), + new LegacyAccuracyCounter(), + new LegacySongProgress(), + new LegacyHealthDisplay(), + new BarHitErrorMeter(), + } + }; + } + + case @"osu": + case @"fruits": + { + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); + + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + }) + { + Children = new Drawable[] + { + new LegacyKeyCounterDisplay(), + } + }; + } + + default: + return null; + } + } + private Texture? getParticleTexture(HitResult result) { switch (result) From 12a9086aa31127b4b81f5a02cbcfe190b4159b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 18:30:18 +0200 Subject: [PATCH 106/579] Fix test failure After the legacy key counter was moved to ruleset-specific component containers, `TestSceneSkinnableHUDOverlay` no longer had a key counter, because it wasn't creating a ruleset-specific HUD component container due to https://github.com/ppy/osu/blob/4983e5f33ed11ba3777e53face6271066ba01ab9/osu.Game/Screens/Play/HUDOverlay.cs#L131-L133 Therefore, to fix, do just enough persuading to make it create one. --- .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 4cb0d5c0ff32..d1e224a91052 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -16,12 +16,13 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Tests.Gameplay; -using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -91,10 +92,7 @@ private void createNew(Action action = null) { SetContents(_ => { - hudOverlay = new HUDOverlay(null, Array.Empty()); - - // Add any key just to display the key counter visually. - hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space)); + hudOverlay = new HUDOverlay(new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()), Array.Empty()); action?.Invoke(hudOverlay); From 0cc6818b21f8bb88746269570e22e9ce399ab74a Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 26 Jul 2024 22:44:50 +0800 Subject: [PATCH 107/579] allow hover to expand `ModCustomisationPanel` --- .../Overlays/Mods/ModCustomisationHeader.cs | 13 ++++++++++ .../Overlays/Mods/ModCustomisationPanel.cs | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index bf10e1351540..fbdce7be6d7c 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -28,6 +29,8 @@ public partial class ModCustomisationHeader : OsuHoverContainer public readonly BindableBool Expanded = new BindableBool(); + protected new ModCustomisationPanel Parent => (ModCustomisationPanel)base.Parent; + public ModCustomisationHeader() { Action = Expanded.Toggle; @@ -91,5 +94,15 @@ protected override void LoadComplete() icon.ScaleTo(v.NewValue ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); }, true); } + + protected override bool OnHover(HoverEvent e) + { + if (Enabled.Value) + { + Parent.UpdateHoverExpansion(true); + } + + return base.OnHover(e); + } } } diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index a1e64e8c49e2..a82e279d01d6 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -175,6 +175,22 @@ private void updateDisplay() content.ResizeHeightTo(header_height, 400, Easing.OutQuint); content.FadeOut(400, Easing.OutSine); } + + expandedByHovering = false; + } + + private bool expandedByHovering = false; + public void UpdateHoverExpansion(bool hovered) + { + if (hovered && !Expanded.Value) + { + Expanded.Value = true; + expandedByHovering = true; + } + else if (!hovered && expandedByHovering) + { + Expanded.Value = false; + } } private void updateMods() @@ -206,6 +222,14 @@ private partial class FocusGrabbingContainer : InputBlockingContainer public override bool RequestsFocus => Expanded.Value; public override bool AcceptsFocus => Expanded.Value; + + public new ModCustomisationPanel Parent => (ModCustomisationPanel)base.Parent; + + protected override void OnHoverLost(HoverLostEvent e) + { + Parent.UpdateHoverExpansion(false); + base.OnHoverLost(e); + } } } } From a3576a55c229f16e3d4e251d566a8b2443fb0ebb Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 26 Jul 2024 22:45:12 +0800 Subject: [PATCH 108/579] add test for hovering `ModCustomisationPanel` --- .../TestSceneModCustomisationPanel.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 9c0d18589281..64ef7891c8f9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,6 +11,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osuTK; namespace osu.Game.Tests.Visual.UserInterface { @@ -19,6 +21,8 @@ public partial class TestSceneModCustomisationPanel : OsuManualInputManagerTestS private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); private ModCustomisationPanel panel = null!; + private ModCustomisationHeader header = null!; + private Container content = null!; [SetUp] public void SetUp() => Schedule(() => @@ -36,6 +40,9 @@ public void SetUp() => Schedule(() => SelectedMods = { BindTarget = SelectedMods }, } }; + + header = panel.Children.OfType().First(); + content = panel.Children.OfType().First(); }); [Test] @@ -62,5 +69,68 @@ public void TestDisplay() panel.Enabled.Value = panel.Expanded.Value = false; }); } + + [Test] + public void TestHoverExpand() + { + // Can not expand by hovering when no supported mod + { + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + + AddAssert("not expanded", () => !panel.Expanded.Value); + + AddStep("hover content", () => InputManager.MoveMouseTo(content)); + + AddAssert("neither expanded", () => !panel.Expanded.Value); + + AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); + } + + AddStep("add customisable mod", () => + { + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + panel.Enabled.Value = true; + }); + + // Can expand by hovering when supported mod + { + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + + AddAssert("expanded", () => panel.Expanded.Value); + + AddStep("hover content", () => InputManager.MoveMouseTo(content)); + + AddAssert("still expanded", () => panel.Expanded.Value); + } + + // Will collapse when mouse left from content + { + AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); + + AddAssert("not expanded", () => !panel.Expanded.Value); + } + + // Will collapse when mouse left from header + { + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + + AddAssert("expanded", () => panel.Expanded.Value); + + AddStep("left from header", () => InputManager.MoveMouseTo(Vector2.One)); + + AddAssert("not expanded", () => !panel.Expanded.Value); + } + + // Not collapse when mouse left if not expanded by hovering + { + AddStep("expand not by hovering", () => panel.Expanded.Value = true); + + AddStep("hover content", () => InputManager.MoveMouseTo(content)); + + AddStep("moust left", () => InputManager.MoveMouseTo(Vector2.One)); + + AddAssert("still expanded", () => panel.Expanded.Value); + } + } } } From aed81d97584353d06431fb7767690fa22ecc3f19 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 26 Jul 2024 22:56:07 +0800 Subject: [PATCH 109/579] make code inspector happy --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 4 ++-- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index fbdce7be6d7c..5a9e6099e6e5 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -29,7 +29,7 @@ public partial class ModCustomisationHeader : OsuHoverContainer public readonly BindableBool Expanded = new BindableBool(); - protected new ModCustomisationPanel Parent => (ModCustomisationPanel)base.Parent; + protected new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; public ModCustomisationHeader() { @@ -99,7 +99,7 @@ protected override bool OnHover(HoverEvent e) { if (Enabled.Value) { - Parent.UpdateHoverExpansion(true); + Parent?.UpdateHoverExpansion(true); } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index a82e279d01d6..ee8232b79a50 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -179,7 +179,8 @@ private void updateDisplay() expandedByHovering = false; } - private bool expandedByHovering = false; + private bool expandedByHovering; + public void UpdateHoverExpansion(bool hovered) { if (hovered && !Expanded.Value) @@ -223,11 +224,11 @@ private partial class FocusGrabbingContainer : InputBlockingContainer public override bool RequestsFocus => Expanded.Value; public override bool AcceptsFocus => Expanded.Value; - public new ModCustomisationPanel Parent => (ModCustomisationPanel)base.Parent; + public new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; protected override void OnHoverLost(HoverLostEvent e) { - Parent.UpdateHoverExpansion(false); + Parent?.UpdateHoverExpansion(false); base.OnHoverLost(e); } } From bd017aea38f48746bb5148d7315e33dd9463f2ee Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 26 Jul 2024 23:58:52 +0800 Subject: [PATCH 110/579] fix `TestPreexistingSelection` failing --- osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index e4622ffcf907..77909d693610 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -57,6 +57,7 @@ public void SetUpSteps() AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0)); AddStep("reset mods", () => SelectedMods.SetDefault()); AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true)); + AddStep("reset mouse", () => InputManager.MoveMouseTo(Vector2.One)); AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo)); AddStep("set up presets", () => { From b97d96fcb0189b736984ec1ecb0fdebc0c55d07d Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Sat, 27 Jul 2024 15:15:14 +0800 Subject: [PATCH 111/579] Fix panel collapse when hovering dropdown menu --- .../Overlays/Mods/ModCustomisationPanel.cs | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index ee8232b79a50..d906e704e0eb 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -81,6 +83,7 @@ private void load() Colour = Color4.Black.Opacity(0.25f), }, Expanded = { BindTarget = Expanded }, + ExpandedByHovering = { BindTarget = ExpandedByHovering }, Children = new Drawable[] { new Box @@ -176,20 +179,22 @@ private void updateDisplay() content.FadeOut(400, Easing.OutSine); } - expandedByHovering = false; + ExpandedByHovering.Value = false; } - private bool expandedByHovering; + public readonly BindableBool ExpandedByHovering = new BindableBool(); public void UpdateHoverExpansion(bool hovered) { if (hovered && !Expanded.Value) { Expanded.Value = true; - expandedByHovering = true; + ExpandedByHovering.Value = true; } - else if (!hovered && expandedByHovering) + else if (!hovered && ExpandedByHovering.Value) { + Debug.Assert(Expanded.Value); + Expanded.Value = false; } } @@ -220,17 +225,35 @@ protected override void Update() private partial class FocusGrabbingContainer : InputBlockingContainer { public IBindable Expanded { get; } = new BindableBool(); + public IBindable ExpandedByHovering { get; } = new BindableBool(); public override bool RequestsFocus => Expanded.Value; public override bool AcceptsFocus => Expanded.Value; public new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; + private InputManager inputManager = null!; + + protected override void LoadComplete() + { + inputManager = GetContainingInputManager(); + } + protected override void OnHoverLost(HoverLostEvent e) { - Parent?.UpdateHoverExpansion(false); + if (ExpandedByHovering.Value && !hasHoveredchild()) + Parent?.UpdateHoverExpansion(false); + base.OnHoverLost(e); } + + private bool hasHoveredchild() + { + return inputManager.HoveredDrawables.Any(parentIsThis); + + bool parentIsThis(Drawable d) + => d is not null && (d == this || parentIsThis(d.Parent)); + } } } } From fc842868a98132c2cedb5952b26cb55feb9b2a7e Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Sat, 27 Jul 2024 16:24:20 +0800 Subject: [PATCH 112/579] fix code quality --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index d906e704e0eb..f17d2f39e6e4 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -236,7 +236,8 @@ private partial class FocusGrabbingContainer : InputBlockingContainer protected override void LoadComplete() { - inputManager = GetContainingInputManager(); + base.LoadComplete(); + inputManager = GetContainingInputManager()!; } protected override void OnHoverLost(HoverLostEvent e) @@ -251,7 +252,7 @@ private bool hasHoveredchild() { return inputManager.HoveredDrawables.Any(parentIsThis); - bool parentIsThis(Drawable d) + bool parentIsThis(Drawable? d) => d is not null && (d == this || parentIsThis(d.Parent)); } } From 77d64e0c3d593d4b912a6fc8d2f1e16a9e46e9b8 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Sat, 27 Jul 2024 17:59:38 +0800 Subject: [PATCH 113/579] replace with `ReceivePositionalInputAt` --- .../Overlays/Mods/ModCustomisationPanel.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index f17d2f39e6e4..9795b61762ce 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -232,29 +231,13 @@ private partial class FocusGrabbingContainer : InputBlockingContainer public new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; - private InputManager inputManager = null!; - - protected override void LoadComplete() - { - base.LoadComplete(); - inputManager = GetContainingInputManager()!; - } - protected override void OnHoverLost(HoverLostEvent e) { - if (ExpandedByHovering.Value && !hasHoveredchild()) + if (ExpandedByHovering.Value && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) Parent?.UpdateHoverExpansion(false); base.OnHoverLost(e); } - - private bool hasHoveredchild() - { - return inputManager.HoveredDrawables.Any(parentIsThis); - - bool parentIsThis(Drawable? d) - => d is not null && (d == this || parentIsThis(d.Parent)); - } } } } From aed2b3c7c681235cc365d01cc8282630558985cb Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Jul 2024 17:20:22 -0700 Subject: [PATCH 114/579] Inherit `GrayButton` instead Also fixes hover highlight. --- osu.Game/Screens/Ranking/CollectionButton.cs | 25 ++----------- osu.Game/Screens/Ranking/FavouriteButton.cs | 37 +++++--------------- 2 files changed, 12 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Ranking/CollectionButton.cs b/osu.Game/Screens/Ranking/CollectionButton.cs index a3e2864c7e52..834326677119 100644 --- a/osu.Game/Screens/Ranking/CollectionButton.cs +++ b/osu.Game/Screens/Ranking/CollectionButton.cs @@ -3,9 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; @@ -15,41 +13,24 @@ namespace osu.Game.Screens.Ranking { - public partial class CollectionButton : OsuAnimatedButton, IHasPopover + public partial class CollectionButton : GrayButton, IHasPopover { - private readonly Box background; - private readonly BeatmapInfo beatmapInfo; public CollectionButton(BeatmapInfo beatmapInfo) + : base(FontAwesome.Solid.Book) { this.beatmapInfo = beatmapInfo; Size = new Vector2(50, 30); - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(13), - Icon = FontAwesome.Solid.Book, - }, - }; - TooltipText = "collections"; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.Green; + Background.Colour = colours.Green; Action = this.ShowPopover; } diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index caa0eddb5500..5f212918541c 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -3,8 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -19,17 +17,14 @@ namespace osu.Game.Screens.Ranking { - public partial class FavouriteButton : OsuAnimatedButton + public partial class FavouriteButton : GrayButton { - private readonly Box background; - private readonly SpriteIcon icon; - public readonly BeatmapSetInfo BeatmapSetInfo; private APIBeatmapSet? beatmapSet; private readonly Bindable current; private PostBeatmapFavouriteRequest? favouriteRequest; - private readonly LoadingLayer loading; + private LoadingLayer loading = null!; private readonly IBindable localUser = new Bindable(); @@ -40,35 +35,21 @@ public partial class FavouriteButton : OsuAnimatedButton private OsuColour colours { get; set; } = null!; public FavouriteButton(BeatmapSetInfo beatmapSetInfo) + : base(FontAwesome.Regular.Heart) { BeatmapSetInfo = beatmapSetInfo; current = new BindableWithCurrent(new BeatmapSetFavouriteState(false, 0)); Size = new Vector2(50, 30); - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue - }, - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(13), - Icon = FontAwesome.Regular.Heart, - }, - loading = new LoadingLayer(true, false), - }; - Action = toggleFavouriteStatus; } [BackgroundDependencyLoader] private void load() { + Add(loading = new LoadingLayer(true, false)); + current.BindValueChanged(_ => updateState(), true); localUser.BindTo(api.LocalUser); @@ -147,14 +128,14 @@ private void updateState() { if (current.Value.Favourited) { - background.Colour = colours.Green; - icon.Icon = FontAwesome.Solid.Heart; + Background.Colour = colours.Green; + Icon.Icon = FontAwesome.Solid.Heart; TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite; } else { - background.Colour = colours.Gray4; - icon.Icon = FontAwesome.Regular.Heart; + Background.Colour = colours.Gray4; + Icon.Icon = FontAwesome.Regular.Heart; TooltipText = BeatmapsetsStrings.ShowDetailsFavourite; } } From b5ff2dab432f7d32200e5f8c53e6d970fcd63e9a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Jul 2024 17:26:31 -0700 Subject: [PATCH 115/579] Move some properties/bindables around --- osu.Game/Screens/Ranking/CollectionPopover.cs | 6 +++--- osu.Game/Screens/Ranking/FavouriteButton.cs | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/CollectionPopover.cs b/osu.Game/Screens/Ranking/CollectionPopover.cs index 2411ab99d8e3..214b8fa8a92c 100644 --- a/osu.Game/Screens/Ranking/CollectionPopover.cs +++ b/osu.Game/Screens/Ranking/CollectionPopover.cs @@ -27,14 +27,14 @@ public CollectionPopover(BeatmapInfo beatmapInfo) : base(false) { this.beatmapInfo = beatmapInfo; + + Margin = new MarginPadding(5); + Body.CornerRadius = 4; } [BackgroundDependencyLoader] private void load() { - Margin = new MarginPadding(5); - Body.CornerRadius = 4; - Children = new[] { new OsuMenu(Direction.Vertical, true) diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index 5f212918541c..09c41e4e2356 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -41,8 +41,6 @@ public FavouriteButton(BeatmapSetInfo beatmapSetInfo) current = new BindableWithCurrent(new BeatmapSetFavouriteState(false, 0)); Size = new Vector2(50, 30); - - Action = toggleFavouriteStatus; } [BackgroundDependencyLoader] @@ -50,6 +48,13 @@ private void load() { Add(loading = new LoadingLayer(true, false)); + Action = toggleFavouriteStatus; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + current.BindValueChanged(_ => updateState(), true); localUser.BindTo(api.LocalUser); From b4ca07300ac2de20aa8f364668cfa6ce613599ae Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Jul 2024 18:32:35 -0700 Subject: [PATCH 116/579] Use same size button for everything --- .../Visual/Ranking/TestSceneCollectionButton.cs | 7 +++---- .../Visual/Ranking/TestSceneFavouriteButton.cs | 5 ----- osu.Game/Screens/Ranking/CollectionButton.cs | 2 +- osu.Game/Screens/Ranking/FavouriteButton.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 11 ++--------- 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs index 4449aae2575f..8bfa74bbceb6 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs @@ -9,7 +9,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Screens.Ranking; -using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking @@ -30,9 +29,9 @@ public void SetUpSteps() Origin = Anchor.Centre, Child = collectionButton = new CollectionButton(beatmapInfo) { - RelativeSizeAxes = Axes.None, - Size = new Vector2(50), - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, }); } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs index a90fbc0c8412..77a63a39957d 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs @@ -9,7 +9,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Ranking; -using osuTK; namespace osu.Game.Tests.Visual.Ranking { @@ -27,8 +26,6 @@ public void SetUpSteps() { AddStep("create button", () => Child = favourite = new FavouriteButton(beatmapSetInfo) { - RelativeSizeAxes = Axes.None, - Size = new Vector2(50), Anchor = Anchor.Centre, Origin = Anchor.Centre, }); @@ -66,8 +63,6 @@ public void TestInvalidBeatmap() { AddStep("make beatmap invalid", () => Child = favourite = new FavouriteButton(invalidBeatmapSetInfo) { - RelativeSizeAxes = Axes.None, - Size = new Vector2(50), Anchor = Anchor.Centre, Origin = Anchor.Centre, }); diff --git a/osu.Game/Screens/Ranking/CollectionButton.cs b/osu.Game/Screens/Ranking/CollectionButton.cs index 834326677119..980a919a2ecc 100644 --- a/osu.Game/Screens/Ranking/CollectionButton.cs +++ b/osu.Game/Screens/Ranking/CollectionButton.cs @@ -22,7 +22,7 @@ public CollectionButton(BeatmapInfo beatmapInfo) { this.beatmapInfo = beatmapInfo; - Size = new Vector2(50, 30); + Size = new Vector2(75, 30); TooltipText = "collections"; } diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index 09c41e4e2356..daa6312020a3 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -40,7 +40,7 @@ public FavouriteButton(BeatmapSetInfo beatmapSetInfo) BeatmapSetInfo = beatmapSetInfo; current = new BindableWithCurrent(new BeatmapSetFavouriteState(false, 0)); - Size = new Vector2(50, 30); + Size = new Vector2(75, 30); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index befd024ccb67..da7a4b1e6b0f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -211,17 +211,10 @@ private void load(AudioManager audio) } if (Score?.BeatmapInfo != null) - { - buttons.Add(new CollectionButton(Score.BeatmapInfo) { Width = 75 }); - } + buttons.Add(new CollectionButton(Score.BeatmapInfo)); if (Score?.BeatmapInfo?.BeatmapSet != null && Score.BeatmapInfo.BeatmapSet.OnlineID > 0) - { - buttons.Add(new FavouriteButton(Score.BeatmapInfo.BeatmapSet) - { - Width = 75 - }); - } + buttons.Add(new FavouriteButton(Score.BeatmapInfo.BeatmapSet)); } protected override void LoadComplete() From 04b15d0d38ad1e1587493f281a14f4053cd7fe4e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Jul 2024 18:35:53 -0700 Subject: [PATCH 117/579] Remove unnecessary `ReceivePositionalInputAt` Results is not even using the new footer. --- osu.Game/Screens/Ranking/CollectionButton.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/CollectionButton.cs b/osu.Game/Screens/Ranking/CollectionButton.cs index 980a919a2ecc..4d5312500524 100644 --- a/osu.Game/Screens/Ranking/CollectionButton.cs +++ b/osu.Game/Screens/Ranking/CollectionButton.cs @@ -35,9 +35,6 @@ private void load(OsuColour colours) Action = this.ShowPopover; } - // use Content for tracking input as some buttons might be temporarily hidden with DisappearToBottom, and they become hidden by moving Content away from screen. - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); - public Popover GetPopover() => new CollectionPopover(beatmapInfo); } } From 334f5fda2d4677fd28696e528461a4b19e7b5e7e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Jul 2024 19:02:57 -0700 Subject: [PATCH 118/579] Remove direct margin set in popover that was causing positioning offset --- osu.Game/Screens/Ranking/CollectionPopover.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/CollectionPopover.cs b/osu.Game/Screens/Ranking/CollectionPopover.cs index 214b8fa8a92c..e285c8005644 100644 --- a/osu.Game/Screens/Ranking/CollectionPopover.cs +++ b/osu.Game/Screens/Ranking/CollectionPopover.cs @@ -28,7 +28,6 @@ public CollectionPopover(BeatmapInfo beatmapInfo) { this.beatmapInfo = beatmapInfo; - Margin = new MarginPadding(5); Body.CornerRadius = 4; } From bc25e5d706f86381069a00344796b7fc20446710 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Jul 2024 19:13:11 -0700 Subject: [PATCH 119/579] Remove unnecessary depth and padding set --- osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs | 1 - osu.Game/Screens/Ranking/ResultsScreen.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs index 8bfa74bbceb6..5b6721bc0fb2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs @@ -23,7 +23,6 @@ public void SetUpSteps() { AddStep("create button", () => Child = new PopoverContainer { - Depth = -1, RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 8b9606d468f1..4481b5f16e32 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -100,9 +100,7 @@ private void load(AudioManager audio) InternalChild = new PopoverContainer { - Depth = -1, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(0), Child = new GridContainer { RelativeSizeAxes = Axes.Both, From 36bd83bb80701da00a017af7a44a6f15cb3394bd Mon Sep 17 00:00:00 2001 From: Layendan Date: Tue, 30 Jul 2024 15:22:41 -0700 Subject: [PATCH 120/579] Update collection state when users add/remove from collection --- osu.Game/Screens/Ranking/CollectionButton.cs | 47 ++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/CollectionButton.cs b/osu.Game/Screens/Ranking/CollectionButton.cs index 4d5312500524..804ffe9f75c3 100644 --- a/osu.Game/Screens/Ranking/CollectionButton.cs +++ b/osu.Game/Screens/Ranking/CollectionButton.cs @@ -1,26 +1,43 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; +using Realms; namespace osu.Game.Screens.Ranking { public partial class CollectionButton : GrayButton, IHasPopover { private readonly BeatmapInfo beatmapInfo; + private readonly Bindable current; + + [Resolved] + private RealmAccess realmAccess { get; set; } = null!; + + private IDisposable? collectionSubscription; + + [Resolved] + private OsuColour colours { get; set; } = null!; public CollectionButton(BeatmapInfo beatmapInfo) : base(FontAwesome.Solid.Book) { this.beatmapInfo = beatmapInfo; + current = new Bindable(false); Size = new Vector2(75, 30); @@ -28,13 +45,37 @@ public CollectionButton(BeatmapInfo beatmapInfo) } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - Background.Colour = colours.Green; - Action = this.ShowPopover; } + protected override void LoadComplete() + { + base.LoadComplete(); + + collectionSubscription = realmAccess.RegisterForNotifications(r => r.All(), updateRealm); + + current.BindValueChanged(_ => updateState(), true); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + collectionSubscription?.Dispose(); + } + + private void updateRealm(IRealmCollection sender, ChangeSet? changes) + { + current.Value = sender.AsEnumerable().Any(c => c.BeatmapMD5Hashes.Contains(beatmapInfo.MD5Hash)); + } + + private void updateState() + { + Background.FadeColour(current.Value ? colours.Green : colours.Gray4, 500, Easing.InOutExpo); + } + public Popover GetPopover() => new CollectionPopover(beatmapInfo); } } From 8eeb5ae06b3204aee8dce0541181e191ca7e175a Mon Sep 17 00:00:00 2001 From: Layendan Date: Tue, 30 Jul 2024 17:08:56 -0700 Subject: [PATCH 121/579] Fix tests --- osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 5 ++--- osu.Game/Tests/Visual/OsuTestScene.cs | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 36e256b9202f..bf29ae944264 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -197,9 +197,8 @@ public bool HandleRequest(APIRequest request, APIUser localUser, BeatmapManager case GetBeatmapSetRequest getBeatmapSetRequest: { - var baseBeatmap = getBeatmapSetRequest.Type == BeatmapSetLookupType.BeatmapId - ? beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID) - : beatmapManager.QueryBeatmap(b => b.BeatmapSet.OnlineID == getBeatmapSetRequest.ID); + // Incorrect logic, see https://github.com/ppy/osu/pull/28991#issuecomment-2256721076 for reason why this change + var baseBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID); if (baseBeatmap == null) { diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 2b4c64dca844..09cfe5ecad0f 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -306,7 +306,9 @@ public static APIBeatmapSet CreateAPIBeatmapSet(IBeatmapInfo original) StarRating = original.StarRating, DifficultyName = original.DifficultyName, } - } + }, + HasFavourited = false, + FavouriteCount = 0, }; foreach (var beatmap in result.Beatmaps) From 5b46597d562ef2526d9f8206ae98af47a5919370 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Wed, 31 Jul 2024 16:54:32 +0800 Subject: [PATCH 122/579] fix click to expand on touch devices --- .../Overlays/Mods/ModCustomisationHeader.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 5a9e6099e6e5..3d6a23043d77 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -95,11 +95,30 @@ protected override void LoadComplete() }, true); } + private bool touchedThisFrame; + + protected override bool OnTouchDown(TouchDownEvent e) + { + if (Enabled.Value) + { + touchedThisFrame = true; + Schedule(() => touchedThisFrame = false); + } + + return base.OnTouchDown(e); + } + protected override bool OnHover(HoverEvent e) { if (Enabled.Value) { - Parent?.UpdateHoverExpansion(true); + if (!touchedThisFrame) + Parent?.UpdateHoverExpansion(true); + } + if (Enabled.Value) + { + if (!touchedThisFrame) + Parent?.UpdateHoverExpansion(true); } return base.OnHover(e); From 5fb364cad6dff7243d03fe8287c4a79590f80104 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Wed, 31 Jul 2024 16:56:25 +0800 Subject: [PATCH 123/579] remove redundant code added accidentally --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 3d6a23043d77..306675a74154 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -115,11 +115,6 @@ protected override bool OnHover(HoverEvent e) if (!touchedThisFrame) Parent?.UpdateHoverExpansion(true); } - if (Enabled.Value) - { - if (!touchedThisFrame) - Parent?.UpdateHoverExpansion(true); - } return base.OnHover(e); } From 19a4cef113fcae9f1807e992c2e746bb0c8c0dad Mon Sep 17 00:00:00 2001 From: Layendan Date: Thu, 1 Aug 2024 02:52:41 -0700 Subject: [PATCH 124/579] update var names and test logic --- osu.Game/Screens/Ranking/CollectionButton.cs | 14 +++++++------- .../Visual/OnlinePlay/TestRoomRequestsHandler.cs | 6 ++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/CollectionButton.cs b/osu.Game/Screens/Ranking/CollectionButton.cs index 804ffe9f75c3..869c6a7ff43f 100644 --- a/osu.Game/Screens/Ranking/CollectionButton.cs +++ b/osu.Game/Screens/Ranking/CollectionButton.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking public partial class CollectionButton : GrayButton, IHasPopover { private readonly BeatmapInfo beatmapInfo; - private readonly Bindable current; + private readonly Bindable isInAnyCollection; [Resolved] private RealmAccess realmAccess { get; set; } = null!; @@ -37,7 +37,7 @@ public CollectionButton(BeatmapInfo beatmapInfo) : base(FontAwesome.Solid.Book) { this.beatmapInfo = beatmapInfo; - current = new Bindable(false); + isInAnyCollection = new Bindable(false); Size = new Vector2(75, 30); @@ -54,9 +54,9 @@ protected override void LoadComplete() { base.LoadComplete(); - collectionSubscription = realmAccess.RegisterForNotifications(r => r.All(), updateRealm); + collectionSubscription = realmAccess.RegisterForNotifications(r => r.All(), collectionsChanged); - current.BindValueChanged(_ => updateState(), true); + isInAnyCollection.BindValueChanged(_ => updateState(), true); } protected override void Dispose(bool isDisposing) @@ -66,14 +66,14 @@ protected override void Dispose(bool isDisposing) collectionSubscription?.Dispose(); } - private void updateRealm(IRealmCollection sender, ChangeSet? changes) + private void collectionsChanged(IRealmCollection sender, ChangeSet? changes) { - current.Value = sender.AsEnumerable().Any(c => c.BeatmapMD5Hashes.Contains(beatmapInfo.MD5Hash)); + isInAnyCollection.Value = sender.AsEnumerable().Any(c => c.BeatmapMD5Hashes.Contains(beatmapInfo.MD5Hash)); } private void updateState() { - Background.FadeColour(current.Value ? colours.Green : colours.Gray4, 500, Easing.InOutExpo); + Background.FadeColour(isInAnyCollection.Value ? colours.Green : colours.Gray4, 500, Easing.InOutExpo); } public Popover GetPopover() => new CollectionPopover(beatmapInfo); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index bf29ae944264..b6ceb6125484 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -9,6 +9,7 @@ using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -197,8 +198,9 @@ public bool HandleRequest(APIRequest request, APIUser localUser, BeatmapManager case GetBeatmapSetRequest getBeatmapSetRequest: { - // Incorrect logic, see https://github.com/ppy/osu/pull/28991#issuecomment-2256721076 for reason why this change - var baseBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID); + var baseBeatmap = getBeatmapSetRequest.Type == BeatmapSetLookupType.BeatmapId + ? beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID) + : beatmapManager.QueryBeatmapSet(s => s.OnlineID == getBeatmapSetRequest.ID)?.PerformRead(s => s.Beatmaps.First().Detach()); if (baseBeatmap == null) { From 188ddbcad68fa67a074c9a0f8860304d27d560b9 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Thu, 1 Aug 2024 18:38:01 +0800 Subject: [PATCH 125/579] pass `ModCustomisationPanel` through ctor --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 7 ++++--- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 13 +++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 306675a74154..9589c7465ffb 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -29,10 +29,11 @@ public partial class ModCustomisationHeader : OsuHoverContainer public readonly BindableBool Expanded = new BindableBool(); - protected new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; + private readonly ModCustomisationPanel panel; - public ModCustomisationHeader() + public ModCustomisationHeader(ModCustomisationPanel panel) { + this.panel = panel; Action = Expanded.Toggle; Enabled.Value = false; } @@ -113,7 +114,7 @@ protected override bool OnHover(HoverEvent e) if (Enabled.Value) { if (!touchedThisFrame) - Parent?.UpdateHoverExpansion(true); + panel.UpdateHoverExpansion(true); } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 9795b61762ce..85991c3a9db9 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -58,7 +58,7 @@ private void load() InternalChildren = new Drawable[] { - new ModCustomisationHeader + new ModCustomisationHeader(this) { Depth = float.MinValue, RelativeSizeAxes = Axes.X, @@ -66,7 +66,7 @@ private void load() Enabled = { BindTarget = Enabled }, Expanded = { BindTarget = Expanded }, }, - content = new FocusGrabbingContainer + content = new FocusGrabbingContainer(this) { RelativeSizeAxes = Axes.X, BorderColour = colourProvider.Dark3, @@ -229,12 +229,17 @@ private partial class FocusGrabbingContainer : InputBlockingContainer public override bool RequestsFocus => Expanded.Value; public override bool AcceptsFocus => Expanded.Value; - public new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; + private readonly ModCustomisationPanel panel; + + public FocusGrabbingContainer(ModCustomisationPanel panel) + { + this.panel = panel; + } protected override void OnHoverLost(HoverLostEvent e) { if (ExpandedByHovering.Value && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - Parent?.UpdateHoverExpansion(false); + panel.UpdateHoverExpansion(false); base.OnHoverLost(e); } From 051d52c23f69db59c3a4270f29c4d7a5fb439838 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Thu, 1 Aug 2024 19:25:45 +0800 Subject: [PATCH 126/579] Update ModCustomisationPanel to use ExpandedState enum --- .../TestSceneFreeModSelectOverlay.cs | 2 +- .../TestSceneModCustomisationPanel.cs | 26 +++---- .../TestSceneModSelectOverlay.cs | 4 +- .../Overlays/Mods/ModCustomisationHeader.cs | 24 +++++-- .../Overlays/Mods/ModCustomisationPanel.cs | 68 ++++++++++--------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++-- 6 files changed, 77 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 497faa28d0d6..3097d245957d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -61,7 +61,7 @@ public void TestCustomisationNotAvailable() AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); AddWaitStep("wait some", 3); - AddAssert("customisation area not expanded", () => !this.ChildrenOfType().Single().Expanded.Value); + AddAssert("customisation area not expanded", () => !this.ChildrenOfType().Single().Expanded); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 64ef7891c8f9..16c9c2bc14c1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -51,22 +51,22 @@ public void TestDisplay() AddStep("set DT", () => { SelectedMods.Value = new[] { new OsuModDoubleTime() }; - panel.Enabled.Value = panel.Expanded.Value = true; + panel.Enabled.Value = panel.Expanded = true; }); AddStep("set DA", () => { SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }; - panel.Enabled.Value = panel.Expanded.Value = true; + panel.Enabled.Value = panel.Expanded = true; }); AddStep("set FL+WU+DA+AD", () => { SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }; - panel.Enabled.Value = panel.Expanded.Value = true; + panel.Enabled.Value = panel.Expanded = true; }); AddStep("set empty", () => { SelectedMods.Value = Array.Empty(); - panel.Enabled.Value = panel.Expanded.Value = false; + panel.Enabled.Value = panel.Expanded = false; }); } @@ -77,11 +77,11 @@ public void TestHoverExpand() { AddStep("hover header", () => InputManager.MoveMouseTo(header)); - AddAssert("not expanded", () => !panel.Expanded.Value); + AddAssert("not expanded", () => !panel.Expanded); AddStep("hover content", () => InputManager.MoveMouseTo(content)); - AddAssert("neither expanded", () => !panel.Expanded.Value); + AddAssert("neither expanded", () => !panel.Expanded); AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); } @@ -96,40 +96,40 @@ public void TestHoverExpand() { AddStep("hover header", () => InputManager.MoveMouseTo(header)); - AddAssert("expanded", () => panel.Expanded.Value); + AddAssert("expanded", () => panel.Expanded); AddStep("hover content", () => InputManager.MoveMouseTo(content)); - AddAssert("still expanded", () => panel.Expanded.Value); + AddAssert("still expanded", () => panel.Expanded); } // Will collapse when mouse left from content { AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); - AddAssert("not expanded", () => !panel.Expanded.Value); + AddAssert("not expanded", () => !panel.Expanded); } // Will collapse when mouse left from header { AddStep("hover header", () => InputManager.MoveMouseTo(header)); - AddAssert("expanded", () => panel.Expanded.Value); + AddAssert("expanded", () => panel.Expanded); AddStep("left from header", () => InputManager.MoveMouseTo(Vector2.One)); - AddAssert("not expanded", () => !panel.Expanded.Value); + AddAssert("not expanded", () => !panel.Expanded); } // Not collapse when mouse left if not expanded by hovering { - AddStep("expand not by hovering", () => panel.Expanded.Value = true); + AddStep("expand not by hovering", () => panel.Expanded = true); AddStep("hover content", () => InputManager.MoveMouseTo(content)); AddStep("moust left", () => InputManager.MoveMouseTo(Vector2.One)); - AddAssert("still expanded", () => panel.Expanded.Value); + AddAssert("still expanded", () => panel.Expanded); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 77909d693610..00575827554d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -999,7 +999,7 @@ public void TestCustomisationPanelAbsorbsInput([Values] bool textSearchStartsAct AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left)); AddAssert("search still not focused", () => !this.ChildrenOfType().Single().HasFocus); AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("customisation panel closed by click", () => !this.ChildrenOfType().Single().Expanded.Value); + AddAssert("customisation panel closed by click", () => !this.ChildrenOfType().Single().Expanded); if (textSearchStartsActive) AddAssert("search focused", () => this.ChildrenOfType().Single().HasFocus); @@ -1022,7 +1022,7 @@ private void changeRuleset(int id) private void assertCustomisationToggleState(bool disabled, bool active) { AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().Enabled.Value == !disabled); - AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().Expanded.Value == active); + AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().Expanded == active); } private T getSelectedMod() where T : Mod => SelectedMods.Value.OfType().Single(); diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 9589c7465ffb..76d2a0deb1fe 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osu.Game.Localisation; +using static osu.Game.Overlays.Mods.ModCustomisationPanel; namespace osu.Game.Overlays.Mods { @@ -27,14 +28,13 @@ public partial class ModCustomisationHeader : OsuHoverContainer protected override IEnumerable EffectTargets => new[] { background }; - public readonly BindableBool Expanded = new BindableBool(); + public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); private readonly ModCustomisationPanel panel; public ModCustomisationHeader(ModCustomisationPanel panel) { this.panel = panel; - Action = Expanded.Toggle; Enabled.Value = false; } @@ -90,12 +90,26 @@ protected override void LoadComplete() : ModSelectOverlayStrings.CustomisationPanelDisabledReason; }, true); - Expanded.BindValueChanged(v => + ExpandedState.BindValueChanged(v => { - icon.ScaleTo(v.NewValue ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); + icon.ScaleTo(v.NewValue > ModCustomisationPanelState.Collapsed ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); }, true); } + protected override bool OnClick(ClickEvent e) + { + if (Enabled.Value) + { + ExpandedState.Value = ExpandedState.Value switch + { + ModCustomisationPanelState.Collapsed => ModCustomisationPanelState.Expanded, + _ => ModCustomisationPanelState.Collapsed + }; + } + + return base.OnClick(e); + } + private bool touchedThisFrame; protected override bool OnTouchDown(TouchDownEvent e) @@ -114,7 +128,7 @@ protected override bool OnHover(HoverEvent e) if (Enabled.Value) { if (!touchedThisFrame) - panel.UpdateHoverExpansion(true); + panel.UpdateHoverExpansion(ModCustomisationPanelState.ExpandedByHover); } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 85991c3a9db9..a551081a7b20 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -39,7 +38,13 @@ public partial class ModCustomisationPanel : OverlayContainer, IKeyBindingHandle public readonly BindableBool Enabled = new BindableBool(); - public readonly BindableBool Expanded = new BindableBool(); + public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); + + public bool Expanded + { + get => ExpandedState.Value > ModCustomisationPanelState.Collapsed; + set => ExpandedState.Value = value ? ModCustomisationPanelState.Expanded : ModCustomisationPanelState.Collapsed; + } public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); @@ -48,8 +53,8 @@ public partial class ModCustomisationPanel : OverlayContainer, IKeyBindingHandle // Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded. // These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside // (returning Expanded.Value to OnHover or overriding Block{Non}PositionalInput doesn't work). - public override bool HandlePositionalInput => Expanded.Value; - public override bool HandleNonPositionalInput => Expanded.Value; + public override bool HandlePositionalInput => Expanded; + public override bool HandleNonPositionalInput => Expanded; [BackgroundDependencyLoader] private void load() @@ -64,7 +69,7 @@ private void load() RelativeSizeAxes = Axes.X, Height = header_height, Enabled = { BindTarget = Enabled }, - Expanded = { BindTarget = Expanded }, + ExpandedState = { BindTarget = ExpandedState }, }, content = new FocusGrabbingContainer(this) { @@ -81,8 +86,7 @@ private void load() Roundness = 5f, Colour = Color4.Black.Opacity(0.25f), }, - Expanded = { BindTarget = Expanded }, - ExpandedByHovering = { BindTarget = ExpandedByHovering }, + ExpandedState = { BindTarget = ExpandedState }, Children = new Drawable[] { new Box @@ -124,7 +128,7 @@ protected override void LoadComplete() this.FadeColour(OsuColour.Gray(e.NewValue ? 1f : 0.6f), 300, Easing.OutQuint); }, true); - Expanded.BindValueChanged(_ => updateDisplay(), true); + ExpandedState.BindValueChanged(_ => updateDisplay(), true); SelectedMods.BindValueChanged(_ => updateMods(), true); FinishTransforms(true); @@ -136,7 +140,7 @@ protected override void LoadComplete() protected override bool OnClick(ClickEvent e) { - Expanded.Value = false; + Expanded = false; return base.OnClick(e); } @@ -149,7 +153,7 @@ public bool OnPressed(KeyBindingPressEvent e) switch (e.Action) { case GlobalAction.Back: - Expanded.Value = false; + Expanded = false; return true; } @@ -164,7 +168,7 @@ private void updateDisplay() { content.ClearTransforms(); - if (Expanded.Value) + if (Expanded) { content.AutoSizeDuration = 400; content.AutoSizeEasing = Easing.OutQuint; @@ -177,30 +181,19 @@ private void updateDisplay() content.ResizeHeightTo(header_height, 400, Easing.OutQuint); content.FadeOut(400, Easing.OutSine); } - - ExpandedByHovering.Value = false; } - public readonly BindableBool ExpandedByHovering = new BindableBool(); - - public void UpdateHoverExpansion(bool hovered) + public void UpdateHoverExpansion(ModCustomisationPanelState state) { - if (hovered && !Expanded.Value) - { - Expanded.Value = true; - ExpandedByHovering.Value = true; - } - else if (!hovered && ExpandedByHovering.Value) - { - Debug.Assert(Expanded.Value); + if (state > ModCustomisationPanelState.Collapsed && state <= ExpandedState.Value) + return; - Expanded.Value = false; - } + ExpandedState.Value = state; } private void updateMods() { - Expanded.Value = false; + Expanded = false; sectionsFlow.Clear(); // Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels). @@ -223,11 +216,10 @@ protected override void Update() private partial class FocusGrabbingContainer : InputBlockingContainer { - public IBindable Expanded { get; } = new BindableBool(); - public IBindable ExpandedByHovering { get; } = new BindableBool(); + public readonly IBindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); - public override bool RequestsFocus => Expanded.Value; - public override bool AcceptsFocus => Expanded.Value; + public override bool RequestsFocus => panel.Expanded; + public override bool AcceptsFocus => panel.Expanded; private readonly ModCustomisationPanel panel; @@ -238,11 +230,21 @@ public FocusGrabbingContainer(ModCustomisationPanel panel) protected override void OnHoverLost(HoverLostEvent e) { - if (ExpandedByHovering.Value && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - panel.UpdateHoverExpansion(false); + if (ExpandedState.Value is ModCustomisationPanelState.ExpandedByHover + && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + { + panel.UpdateHoverExpansion(ModCustomisationPanelState.Collapsed); + } base.OnHoverLost(e); } } + + public enum ModCustomisationPanelState + { + Collapsed = 0, + ExpandedByHover = 1, + Expanded = 2, + } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 746959089520..109d81f7795c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -237,7 +237,7 @@ protected override void LoadComplete() ActiveMods.Value = ComputeActiveMods(); }, true); - customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true); + customisationPanel.ExpandedState.BindValueChanged(_ => updateCustomisationVisualState(), true); SearchTextBox.Current.BindValueChanged(query => { @@ -368,18 +368,18 @@ private void updateCustomisation() customisationPanel.Enabled.Value = true; if (anyModPendingConfiguration) - customisationPanel.Expanded.Value = true; + customisationPanel.Expanded = true; } else { - customisationPanel.Expanded.Value = false; + customisationPanel.Expanded = false; customisationPanel.Enabled.Value = false; } } private void updateCustomisationVisualState() { - if (customisationPanel.Expanded.Value) + if (customisationPanel.Expanded) { columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); @@ -544,7 +544,7 @@ protected override void PopOut() nonFilteredColumnCount += 1; } - customisationPanel.Expanded.Value = false; + customisationPanel.Expanded = false; } #endregion @@ -571,7 +571,7 @@ public override bool OnPressed(KeyBindingPressEvent e) // wherein activating the binding will both change the contents of the search text box and deselect all mods. case GlobalAction.DeselectAllMods: { - if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value) + if (!SearchTextBox.HasFocus && !customisationPanel.Expanded) { DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick(); return true; @@ -637,7 +637,7 @@ protected override bool OnKeyDown(KeyDownEvent e) if (e.Repeat || e.Key != Key.Tab) return false; - if (customisationPanel.Expanded.Value) + if (customisationPanel.Expanded) return true; // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) From de6d8e7eb71f50d8f6708fa4caca1e028b500ec1 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:07:35 +0800 Subject: [PATCH 127/579] Add the custom context menu to handle the key event --- .../Components/EditorContextMenuContainer.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs diff --git a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs new file mode 100644 index 000000000000..3207cb084903 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs @@ -0,0 +1,36 @@ +// 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.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Components +{ + public partial class EditorContextMenuContainer : OsuContextMenuContainer, IKeyBindingHandler + { + public override bool ChangeFocusOnClick => true; + + private OsuContextMenu menu = null!; + + protected override Framework.Graphics.UserInterface.Menu CreateMenu() => menu = new OsuContextMenu(true); + + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case PlatformAction.Delete: + menu.Close(); + break; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} From 83aeb27c7356f0f8e7b561e79608a089181df5ca Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:08:31 +0800 Subject: [PATCH 128/579] Replace original menu container to the custom one --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 3 ++- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 16d11ccd1afe..96b11b44311f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -213,7 +213,8 @@ public bool OnPressed(KeyBindingPressEvent e) { case PlatformAction.Delete: DeleteSelected(); - return true; + // Pass to the `EditorContextMenuContainer` to handle the menu close + return false; } return false; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d40db329ec15..7eed8809a336 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,7 +31,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -43,6 +42,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -319,7 +319,7 @@ private void load(OsuConfigManager config) editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - AddInternal(new OsuContextMenuContainer + AddInternal(new EditorContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 1ff0c7cb46947597b69e6d0f40fbcab8a39e50f1 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:10:49 +0800 Subject: [PATCH 129/579] Replace original menu container with custom one --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 484af34603ff..1b5588ef5739 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -115,7 +115,7 @@ private void load(EditorClipboard clipboard) { RelativeSizeAxes = Axes.Both; - InternalChild = new OsuContextMenuContainer + InternalChild = new EditorContextMenuContainer { RelativeSizeAxes = Axes.Both, Child = new GridContainer diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7eed8809a336..d40db329ec15 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,6 +31,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -42,7 +43,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -319,7 +319,7 @@ private void load(OsuConfigManager config) editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - AddInternal(new EditorContextMenuContainer + AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 5c5fcd7e7ebdb2ed1aac89d1d31ecb90fdbbd824 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:11:21 +0800 Subject: [PATCH 130/579] Allow key event pass through selection handler --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 96b11b44311f..16d11ccd1afe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -213,8 +213,7 @@ public bool OnPressed(KeyBindingPressEvent e) { case PlatformAction.Delete: DeleteSelected(); - // Pass to the `EditorContextMenuContainer` to handle the menu close - return false; + return true; } return false; From 27d6c4cecb27cfddd87f2d228c4f305cac472777 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:16:14 +0800 Subject: [PATCH 131/579] Implement on beatmap editor --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 16d11ccd1afe..808e9c71e822 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -213,7 +213,7 @@ public bool OnPressed(KeyBindingPressEvent e) { case PlatformAction.Delete: DeleteSelected(); - return true; + return false; } return false; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d40db329ec15..4e5abf2f8254 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -43,6 +43,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -319,7 +320,7 @@ private void load(OsuConfigManager config) editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - AddInternal(new OsuContextMenuContainer + AddInternal(new EditorContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 3cc54667742c51bfa18565f92c400c98c5469354 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:39:06 +0800 Subject: [PATCH 132/579] Refactor the code to follow IoC principle and more flexible --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 168 ++++++++--------- .../Components/EditorContextMenuContainer.cs | 19 +- .../Compose/Components/SelectionHandler.cs | 10 +- osu.Game/Screens/Edit/Editor.cs | 173 +++++++++--------- 4 files changed, 188 insertions(+), 182 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 1b5588ef5739..515ab45f55cf 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -23,7 +23,6 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Dialog; @@ -101,6 +100,12 @@ public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBind [Resolved] private IDialogOverlay? dialogOverlay { get; set; } + [Cached] + public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + }; + public SkinEditor() { } @@ -115,114 +120,111 @@ private void load(EditorClipboard clipboard) { RelativeSizeAxes = Axes.Both; - InternalChild = new EditorContextMenuContainer + ContextMenuContainer.Child = new GridContainer { RelativeSizeAxes = Axes.Both, - Child = new GridContainer + RowDimensions = new[] { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - - Content = new[] + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - new Drawable[] + new Container { - new Container + Name = @"Menu container", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = MENU_HEIGHT, + Children = new Drawable[] { - Name = @"Menu container", - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = MENU_HEIGHT, - Children = new Drawable[] + new EditorMenuBar { - new EditorMenuBar + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] + new MenuItem(CommonStrings.MenuBarFile) { - new MenuItem(CommonStrings.MenuBarFile) + Items = new OsuMenuItem[] { - Items = new OsuMenuItem[] - { - new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), - new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), - }, + new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), + new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), }, - new MenuItem(CommonStrings.MenuBarEdit) + }, + new MenuItem(CommonStrings.MenuBarEdit) + { + Items = new OsuMenuItem[] { - Items = new OsuMenuItem[] - { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - } - }, - headerText = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopRight, - Padding = new MarginPadding(5), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, + } + }, + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, }, }, }, - new Drawable[] + }, + new Drawable[] + { + new SkinEditorSceneLibrary { - new SkinEditorSceneLibrary - { - RelativeSizeAxes = Axes.X, - }, + RelativeSizeAxes = Axes.X, }, - new Drawable[] + }, + new Drawable[] + { + new GridContainer { - new GridContainer + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] { - new Drawable[] + componentsSidebar = new EditorSidebar(), + content = new Container { - componentsSidebar = new EditorSidebar(), - content = new Container - { - Depth = float.MaxValue, - RelativeSizeAxes = Axes.Both, - }, - settingsSidebar = new EditorSidebar(), - } + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, + settingsSidebar = new EditorSidebar(), } } - }, - } + } + }, } }; + AddInternal(ContextMenuContainer); + clipboardContent = clipboard.Content.GetBoundCopy(); } diff --git a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs index 3207cb084903..fa855100d766 100644 --- a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs +++ b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs @@ -1,15 +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 osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit.Components { - public partial class EditorContextMenuContainer : OsuContextMenuContainer, IKeyBindingHandler + public partial class EditorContextMenuContainer : OsuContextMenuContainer { public override bool ChangeFocusOnClick => true; @@ -17,20 +14,14 @@ public partial class EditorContextMenuContainer : OsuContextMenuContainer, IKeyB protected override Framework.Graphics.UserInterface.Menu CreateMenu() => menu = new OsuContextMenu(true); - public bool OnPressed(KeyBindingPressEvent e) + public void ShowMenu() { - switch (e.Action) - { - case PlatformAction.Delete: - menu.Close(); - break; - } - - return false; + menu.Show(); } - public void OnReleased(KeyBindingReleaseEvent e) + public void CloseMenu() { + menu.Close(); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 808e9c71e822..45aa50afbf31 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -20,6 +20,7 @@ using osu.Game.Input.Bindings; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Input; @@ -59,6 +60,9 @@ public abstract partial class SelectionHandler : CompositeDrawable, IKeyBindi public SelectionScaleHandler ScaleHandler { get; private set; } + [Resolved] + private EditorContextMenuContainer editorContextMenuContainer { get; set; } + protected SelectionHandler() { selectedBlueprints = new List>(); @@ -230,7 +234,11 @@ public void OnReleased(KeyBindingReleaseEvent e) /// /// Deselect all selected items. /// - protected void DeselectAll() => SelectedItems.Clear(); + protected void DeselectAll() + { + SelectedItems.Clear(); + editorContextMenuContainer.CloseMenu(); + } /// /// Handle a blueprint becoming selected. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 4e5abf2f8254..45c3dfe896d7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -77,6 +77,12 @@ public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler public const float WAVEFORM_VISUAL_OFFSET = 20; + [Cached] + public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer() + { + RelativeSizeAxes = Axes.Both + }; + public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; @@ -320,109 +326,108 @@ private void load(OsuConfigManager config) editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - AddInternal(new EditorContextMenuContainer + ContextMenuContainer.AddRange(new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Container { - new Container + Name = "Screen container", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 40, Bottom = 50 }, + Child = screenContainer = new Container { - Name = "Screen container", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 50 }, - Child = screenContainer = new Container - { - RelativeSizeAxes = Axes.Both, - } - }, - new Container + } + }, + new Container + { + Name = "Top bar", + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] { - Name = "Top bar", - RelativeSizeAxes = Axes.X, - Height = 40, - Children = new Drawable[] + new EditorMenuBar { - new EditorMenuBar + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + MaxHeight = 600, + Items = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - MaxHeight = 600, - Items = new[] + new MenuItem(CommonStrings.MenuBarFile) + { + Items = createFileMenuItems().ToList() + }, + new MenuItem(CommonStrings.MenuBarEdit) { - new MenuItem(CommonStrings.MenuBarFile) + Items = new[] { - Items = createFileMenuItems().ToList() - }, - new MenuItem(CommonStrings.MenuBarEdit) + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, + new MenuItem(CommonStrings.MenuBarView) + { + Items = new[] { - Items = new[] + new MenuItem(EditorStrings.Timeline) { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - new MenuItem(CommonStrings.MenuBarView) - { - Items = new[] + Items = + [ + new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) + { + State = { BindTarget = editorTimelineShowTimingChanges } + }, + new ToggleMenuItem(EditorStrings.TimelineShowTicks) + { + State = { BindTarget = editorTimelineShowTicks } + }, + ] + }, + new BackgroundDimMenuItem(editorBackgroundDim), + new ToggleMenuItem(EditorStrings.ShowHitMarkers) { - new MenuItem(EditorStrings.Timeline) - { - Items = - [ - new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), - new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) - { - State = { BindTarget = editorTimelineShowTimingChanges } - }, - new ToggleMenuItem(EditorStrings.TimelineShowTicks) - { - State = { BindTarget = editorTimelineShowTicks } - }, - ] - }, - new BackgroundDimMenuItem(editorBackgroundDim), - new ToggleMenuItem(EditorStrings.ShowHitMarkers) - { - State = { BindTarget = editorHitMarkers }, - }, - new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) - { - State = { BindTarget = editorAutoSeekOnPlacement }, - }, - new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) - { - State = { BindTarget = editorLimitedDistanceSnap }, - } - } - }, - new MenuItem(EditorStrings.Timing) - { - Items = new MenuItem[] + State = { BindTarget = editorHitMarkers }, + }, + new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) + { + State = { BindTarget = editorAutoSeekOnPlacement }, + }, + new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) { - new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) + State = { BindTarget = editorLimitedDistanceSnap }, } } + }, + new MenuItem(EditorStrings.Timing) + { + Items = new MenuItem[] + { + new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) + } } - }, - screenSwitcher = new EditorScreenSwitcherControl - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - X = -10, - Current = Mode, - }, + } + }, + screenSwitcher = new EditorScreenSwitcherControl + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -10, + Current = Mode, }, }, - bottomBar = new BottomBar(), - MutationTracker, - } + }, + bottomBar = new BottomBar(), + MutationTracker, }); + + AddInternal(ContextMenuContainer); + changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); From 5d31171fb0ff3d01b034c655c3995489340236ef Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:43:43 +0800 Subject: [PATCH 133/579] Fix code quality --- osu.Game/Screens/Edit/Editor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 45c3dfe896d7..0a944d06270e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,7 +31,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -78,7 +77,7 @@ public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler Date: Sun, 4 Aug 2024 19:56:41 +0800 Subject: [PATCH 134/579] Cleanup code --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 45aa50afbf31..7335096120fc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -217,7 +217,7 @@ public bool OnPressed(KeyBindingPressEvent e) { case PlatformAction.Delete: DeleteSelected(); - return false; + return true; } return false; From 2145368d17f5740f792c8b721ae3e111823c0f94 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 21:00:43 +0800 Subject: [PATCH 135/579] Merge `EditorContextMenuContainer` into `OsuContextMenuContainer` --- .../Cursor/OsuContextMenuContainer.cs | 13 ++++++--- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 ++- .../Components/EditorContextMenuContainer.cs | 27 ------------------- .../Compose/Components/BlueprintContainer.cs | 4 +++ .../Compose/Components/SelectionHandler.cs | 4 +-- osu.Game/Screens/Edit/Editor.cs | 4 +-- 6 files changed, 20 insertions(+), 35 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index c5bcfcd2df0e..85b24cb6a3fc 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -13,11 +13,18 @@ public partial class OsuContextMenuContainer : ContextMenuContainer [Cached] private OsuContextMenuSamples samples = new OsuContextMenuSamples(); - public OsuContextMenuContainer() + private OsuContextMenu menu = null!; + + protected override Menu CreateMenu() => menu = new OsuContextMenu(true); + + public void ShowMenu() { - AddInternal(samples); + menu.Show(); } - protected override Menu CreateMenu() => new OsuContextMenu(true); + public void CloseMenu() + { + menu.Close(); + } } } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 515ab45f55cf..bb2d93f887d1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -32,6 +32,7 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; +using osu.Game.Graphics.Cursor; namespace osu.Game.Overlays.SkinEditor { @@ -101,7 +102,7 @@ public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBind private IDialogOverlay? dialogOverlay { get; set; } [Cached] - public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer + public OsuContextMenuContainer ContextMenuContainer { get; private set; } = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs deleted file mode 100644 index fa855100d766..000000000000 --- a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs +++ /dev/null @@ -1,27 +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.Graphics.Cursor; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Components -{ - public partial class EditorContextMenuContainer : OsuContextMenuContainer - { - public override bool ChangeFocusOnClick => true; - - private OsuContextMenu menu = null!; - - protected override Framework.Graphics.UserInterface.Menu CreateMenu() => menu = new OsuContextMenu(true); - - public void ShowMenu() - { - menu.Show(); - } - - public void CloseMenu() - { - menu.Close(); - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c66be9060515..e8228872d9fe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -15,6 +15,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -46,6 +47,9 @@ public abstract partial class BlueprintContainer : CompositeDrawable, IKeyBin protected readonly BindableList SelectedItems = new BindableList(); + [Resolved(CanBeNull = true)] + private OsuContextMenuContainer contextMenuContainer { get; set; } + protected BlueprintContainer() { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7335096120fc..48876278f796 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -16,11 +16,11 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Edit; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Input; @@ -61,7 +61,7 @@ public abstract partial class SelectionHandler : CompositeDrawable, IKeyBindi public SelectionScaleHandler ScaleHandler { get; private set; } [Resolved] - private EditorContextMenuContainer editorContextMenuContainer { get; set; } + private OsuContextMenuContainer editorContextMenuContainer { get; set; } protected SelectionHandler() { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0a944d06270e..b1a066afb774 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,6 +31,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -42,7 +43,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -77,7 +77,7 @@ public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler Date: Sun, 4 Aug 2024 21:12:09 +0800 Subject: [PATCH 136/579] Fix unit test --- .../Visual/Editing/TestSceneComposeScreen.cs | 2 ++ .../Editing/TestSceneHitObjectComposer.cs | 20 ++++++++++++++----- .../Visual/Editing/TimelineTestScene.cs | 9 +++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 035092ecb72b..7405433e7337 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -52,6 +53,7 @@ public void SetUpSteps() (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()) }, Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index f392841ac739..fac47deec9e3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -12,6 +13,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -70,13 +72,21 @@ public void SetUpSteps() AddStep("Create composer", () => { - Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) + Child = new DependencyProvidingContainer { - Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { - // force the composer to fully overlap the playfield area by setting a 4:3 aspect ratio. - FillMode = FillMode.Fit, - FillAspectRatio = 4 / 3f + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()) + }, + Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) + { + Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) + { + // force the composer to fully overlap the playfield area by setting a 4:3 aspect ratio. + FillMode = FillMode.Fit, + FillAspectRatio = 4 / 3f + } } }; }); diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index cb45ad5a076e..14afd3eac104 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Storyboards; using osuTK; @@ -51,7 +52,7 @@ protected override void LoadComplete() Composer.Alpha = 0; - Add(new OsuContextMenuContainer + var contextMenuContainer = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -75,7 +76,11 @@ protected override void LoadComplete() Origin = Anchor.Centre, } } - }); + }; + + Dependencies.Cache(contextMenuContainer); + + Add(contextMenuContainer); } [SetUpSteps] From 7cebf4c3d23cf49c11ddf079512154e1d0fc0618 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 21:18:03 +0800 Subject: [PATCH 137/579] Fix code quality --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 14afd3eac104..2323612e89bd 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Storyboards; using osuTK; From b32d97b4c055d9b3ba5618673cda22cf039ca4b3 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 21:27:25 +0800 Subject: [PATCH 138/579] Remove decreapted property --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index e8228872d9fe..c66be9060515 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -15,7 +15,6 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -47,9 +46,6 @@ public abstract partial class BlueprintContainer : CompositeDrawable, IKeyBin protected readonly BindableList SelectedItems = new BindableList(); - [Resolved(CanBeNull = true)] - private OsuContextMenuContainer contextMenuContainer { get; set; } - protected BlueprintContainer() { RelativeSizeAxes = Axes.Both; From 2720bcf285813e53af3f21cbbb220927c6d4f357 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 22:37:54 +0800 Subject: [PATCH 139/579] Fix ruleset unit test --- .../Editor/TestSceneManiaComposeScreen.cs | 2 ++ .../Editor/TestSceneManiaHitObjectComposer.cs | 11 ++++++++++- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 2 ++ .../Editor/TestSceneTaikoHitObjectComposer.cs | 11 ++++++++++- .../TestSceneHitObjectComposerDistanceSnapping.cs | 10 ++++++++-- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 8f623d1fc68e..a2f867077402 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; +using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; @@ -52,6 +53,7 @@ public void SetUpSteps() (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), }, Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index d88f48858230..802b2fe84396 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -11,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; @@ -37,7 +39,14 @@ public void Setup() => Schedule(() => BeatDivisor.Value = 8; EditorClock.Seek(0); - Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; + Child = new DependencyProvidingContainer + { + CachedDependencies = new (Type, object)[] + { + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), + }, + Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }, + }; }); [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index b70f93291328..b57496673ba8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -16,6 +16,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Beatmaps; @@ -79,6 +80,7 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(composer.DistanceSnapProvider); + dependencies.Cache(new OsuContextMenuContainer()); return dependencies; } diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs index 64a29ce8667f..7c379eb43c21 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Edit; @@ -22,7 +24,14 @@ public void Setup() => Schedule(() => BeatDivisor.Value = 8; EditorClock.Seek(0); - Child = new TestComposer { RelativeSizeAxes = Axes.Both }; + Child = new DependencyProvidingContainer + { + CachedDependencies = new (Type, object)[] + { + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), + }, + Child = new TestComposer { RelativeSizeAxes = Axes.Both }, + }; }); [Test] diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 12b7dbbf126e..ad6aef63020c 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -10,6 +11,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -52,9 +54,13 @@ public TestSceneHitObjectComposerDistanceSnapping() [SetUp] public void Setup() => Schedule(() => { - Children = new Drawable[] + Child = new DependencyProvidingContainer { - composer = new TestHitObjectComposer() + CachedDependencies = new (Type, object)[] + { + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), + }, + Child = composer = new TestHitObjectComposer(), }; BeatDivisor.Value = 1; From 1b25633e4791c46261614f676c98753ea1b5c2ac Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 23:45:42 +0800 Subject: [PATCH 140/579] Fix headless test --- .../Editor/TestSceneManiaHitObjectComposer.cs | 12 ++++-------- .../TestSceneHitObjectComposerDistanceSnapping.cs | 12 ++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 802b2fe84396..56ad0a24237a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -33,20 +33,16 @@ public partial class TestSceneManiaHitObjectComposer : EditorClockTestScene { private TestComposer composer; + [Cached] + public readonly OsuContextMenuContainer ContextMenuContainer = new OsuContextMenuContainer(); + [SetUp] public void Setup() => Schedule(() => { BeatDivisor.Value = 8; EditorClock.Seek(0); - Child = new DependencyProvidingContainer - { - CachedDependencies = new (Type, object)[] - { - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), - }, - Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }, - }; + Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; }); [Test] diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index ad6aef63020c..83c660bd4d1b 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -33,6 +33,9 @@ public partial class TestSceneHitObjectComposerDistanceSnapping : EditorClockTes [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + public readonly OsuContextMenuContainer ContextMenuContainer = new OsuContextMenuContainer(); + protected override Container Content { get; } = new PopoverContainer { RelativeSizeAxes = Axes.Both }; public TestSceneHitObjectComposerDistanceSnapping() @@ -54,14 +57,7 @@ public TestSceneHitObjectComposerDistanceSnapping() [SetUp] public void Setup() => Schedule(() => { - Child = new DependencyProvidingContainer - { - CachedDependencies = new (Type, object)[] - { - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), - }, - Child = composer = new TestHitObjectComposer(), - }; + Child = composer = new TestHitObjectComposer(); BeatDivisor.Value = 1; From 2098fb8a9dc07a81102097f789512cd3333c0007 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 00:08:02 +0800 Subject: [PATCH 141/579] Fix code quality --- .../Editor/TestSceneManiaHitObjectComposer.cs | 1 - .../Editing/TestSceneHitObjectComposerDistanceSnapping.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 56ad0a24237a..c2364cce1abd 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 83c660bd4d1b..e5e7d0f8a729 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; From b0757a13c20557abc256c9e4937c747dea7d1421 Mon Sep 17 00:00:00 2001 From: kstefanowicz Date: Sun, 4 Aug 2024 12:32:08 -0400 Subject: [PATCH 142/579] Add "enter" hint to chatbox placeholder text while in-game --- osu.Game/Localisation/ChatStrings.cs | 5 +++++ .../Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index 6b0a6bd8e1e7..3b1fc6000a58 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -24,6 +24,11 @@ public static class ChatStrings /// public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention"); + /// + /// "press enter to type message..." + /// + public static LocalisableString IngameInputPlaceholder => new TranslatableString(getKey("input.ingameplaceholder"), "press enter to type message..."); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index d003110039f8..cccab46d98b6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; @@ -42,6 +43,7 @@ public GameplayChatDisplay(Room room) Background.Alpha = 0.2f; TextBox.FocusLost = () => expandedFromTextBoxFocus.Value = false; + TextBox.PlaceholderText = ChatStrings.IngameInputPlaceholder; } protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor. From f92e2094c166508e7e27a56a4d3a6ed07b52c120 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Aug 2024 12:29:56 +0900 Subject: [PATCH 143/579] Adjust localisation string name + formatting --- osu.Game/Localisation/ChatStrings.cs | 2 +- osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index 3b1fc6000a58..f7a36d9570e1 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -27,7 +27,7 @@ public static class ChatStrings /// /// "press enter to type message..." /// - public static LocalisableString IngameInputPlaceholder => new TranslatableString(getKey("input.ingameplaceholder"), "press enter to type message..."); + public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to type message..."); private static string getKey(string key) => $"{prefix}:{key}"; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index cccab46d98b6..656071ad43f4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -43,7 +43,7 @@ public GameplayChatDisplay(Room room) Background.Alpha = 0.2f; TextBox.FocusLost = () => expandedFromTextBoxFocus.Value = false; - TextBox.PlaceholderText = ChatStrings.IngameInputPlaceholder; + TextBox.PlaceholderText = ChatStrings.InGameInputPlaceholder; } protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor. From 0557b9ab7959deceee5c1ae3ac7bfdf6dc1fe192 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 13:20:44 +0900 Subject: [PATCH 144/579] Allow placement deletion with middle mouse This is in addition to Shift + Right-click. I thik middle mouse feels more natural and is a good permanent solution to this issue. Note that this also *allows triggering the context menu from placement mode*. Until now it's done nothing. This may be annoying to users with muscle memory but I want to make the change and harvest feedback. I think showing the context menu is more correct behaviour (although arguably it should return to placement mode on dismiss?). --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 +--- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 2817e26abd03..60b979da5998 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -209,9 +209,7 @@ protected override bool Handle(UIEvent e) case MouseButtonEvent mouse: // placement blueprints should generally block mouse from reaching underlying components (ie. performing clicks on interface buttons). - // for now, the one exception we want to allow is when using a non-main mouse button when shift is pressed, which is used to trigger object deletion - // while in placement mode. - return mouse.Button == MouseButton.Left || !mouse.ShiftPressed; + return mouse.Button == MouseButton.Left || PlacementActive == PlacementState.Active; default: return false; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 16d11ccd1afe..eff6629307a4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -263,7 +263,7 @@ internal virtual void HandleDeselected(SelectionBlueprint blueprint) /// Whether a selection was performed. internal virtual bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { - if (e.ShiftPressed && e.Button == MouseButton.Right) + if (e.Button == MouseButton.Middle || (e.ShiftPressed && e.Button == MouseButton.Right)) { handleQuickDeletion(blueprint); return true; From 9673985e2cc03247afd25ecf80d58774a3c7bbcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 14:00:56 +0900 Subject: [PATCH 145/579] Add test coverage of right/middle click behaviours with placement blueprints --- .../Editing/TestScenePlacementBlueprint.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index e9b442f8dd4a..772a970b5d3d 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -28,6 +28,51 @@ public partial class TestScenePlacementBlueprint : EditorTestScene private GlobalActionContainer globalActionContainer => this.ChildrenOfType().Single(); + [Test] + public void TestDeleteUsingMiddleMouse() + { + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + + AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items); + AddStep("delete with middle mouse", () => InputManager.Click(MouseButton.Middle)); + AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty); + } + + [Test] + public void TestDeleteUsingShiftRightClick() + { + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + + AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items); + AddStep("delete with right mouse", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Click(MouseButton.Right); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty); + } + + [Test] + public void TestContextMenu() + { + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + + AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items); + AddStep("delete with right mouse", () => + { + InputManager.Click(MouseButton.Right); + }); + AddAssert("circle not removed", () => EditorBeatmap.HitObjects, () => Has.One.Items); + AddAssert("circle selected", () => EditorBeatmap.SelectedHitObjects, () => Has.One.Items); + } + [Test] public void TestCommitPlacementViaGlobalAction() { From c0814c2749a467004fe772d199d591d1d8f60d30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 14:24:58 +0900 Subject: [PATCH 146/579] Add test of existing slider placement behaviour for safety --- .../Editing/TestScenePlacementBlueprint.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index 772a970b5d3d..8173536ba401 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -73,6 +73,29 @@ public void TestContextMenu() AddAssert("circle selected", () => EditorBeatmap.SelectedHitObjects, () => Has.One.Items); } + [Test] + [Solo] + public void TestCommitPlacementViaRightClick() + { + Playfield playfield = null!; + + AddStep("select slider placement tool", () => InputManager.Key(Key.Number3)); + AddStep("move mouse to top left of playfield", () => + { + playfield = this.ChildrenOfType().Single(); + var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4; + InputManager.MoveMouseTo(location); + }); + AddStep("begin placement", () => InputManager.Click(MouseButton.Left)); + AddStep("move mouse to bottom right of playfield", () => + { + var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4; + InputManager.MoveMouseTo(location); + }); + AddStep("confirm via right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); + } + [Test] public void TestCommitPlacementViaGlobalAction() { From 6d385c6510855f292583ac56db6116865833cc4d Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 16:31:15 +0800 Subject: [PATCH 147/579] Remove the meaningless `OpenMenu` method --- osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index 85b24cb6a3fc..c9d89a920675 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -17,11 +17,6 @@ public partial class OsuContextMenuContainer : ContextMenuContainer protected override Menu CreateMenu() => menu = new OsuContextMenu(true); - public void ShowMenu() - { - menu.Show(); - } - public void CloseMenu() { menu.Close(); From 75c0c6a5f9dd18ced06b4b05dc9288d892c81580 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 16:32:49 +0800 Subject: [PATCH 148/579] Make the `OsuContextMenu` nullable in `SelectionHandler` --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 48876278f796..21cd2e891f5f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -60,8 +60,8 @@ public abstract partial class SelectionHandler : CompositeDrawable, IKeyBindi public SelectionScaleHandler ScaleHandler { get; private set; } - [Resolved] - private OsuContextMenuContainer editorContextMenuContainer { get; set; } + [Resolved(CanBeNull = true)] + protected OsuContextMenuContainer ContextMenuContainer { get; private set; } protected SelectionHandler() { @@ -237,7 +237,7 @@ public void OnReleased(KeyBindingReleaseEvent e) protected void DeselectAll() { SelectedItems.Clear(); - editorContextMenuContainer.CloseMenu(); + ContextMenuContainer?.CloseMenu(); } /// From 3c8d0ce59f93dcc9df88479d147aa9f3eefce581 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 16:40:31 +0800 Subject: [PATCH 149/579] Revert the unit test changes --- .../Editor/TestSceneManiaComposeScreen.cs | 2 -- .../Editor/TestSceneManiaHitObjectComposer.cs | 4 ---- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 2 -- .../Editor/TestSceneTaikoHitObjectComposer.cs | 11 +---------- ...stSceneHitObjectComposerDistanceSnapping.cs | 4 ---- .../Visual/Editing/TestSceneComposeScreen.cs | 2 -- .../Editing/TestSceneHitObjectComposer.cs | 18 +++++------------- 7 files changed, 6 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index a2f867077402..8f623d1fc68e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; -using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; @@ -53,7 +52,6 @@ public void SetUpSteps() (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), }, Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index c2364cce1abd..d88f48858230 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; @@ -32,9 +31,6 @@ public partial class TestSceneManiaHitObjectComposer : EditorClockTestScene { private TestComposer composer; - [Cached] - public readonly OsuContextMenuContainer ContextMenuContainer = new OsuContextMenuContainer(); - [SetUp] public void Setup() => Schedule(() => { diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index b57496673ba8..b70f93291328 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -16,7 +16,6 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Beatmaps; @@ -80,7 +79,6 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(composer.DistanceSnapProvider); - dependencies.Cache(new OsuContextMenuContainer()); return dependencies; } diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs index 7c379eb43c21..64a29ce8667f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Edit; @@ -24,14 +22,7 @@ public void Setup() => Schedule(() => BeatDivisor.Value = 8; EditorClock.Seek(0); - Child = new DependencyProvidingContainer - { - CachedDependencies = new (Type, object)[] - { - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), - }, - Child = new TestComposer { RelativeSizeAxes = Axes.Both }, - }; + Child = new TestComposer { RelativeSizeAxes = Axes.Both }; }); [Test] diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index e5e7d0f8a729..cf8c3c6ef10f 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -10,7 +10,6 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -32,9 +31,6 @@ public partial class TestSceneHitObjectComposerDistanceSnapping : EditorClockTes [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; - [Cached] - public readonly OsuContextMenuContainer ContextMenuContainer = new OsuContextMenuContainer(); - protected override Container Content { get; } = new PopoverContainer { RelativeSizeAxes = Axes.Both }; public TestSceneHitObjectComposerDistanceSnapping() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 7405433e7337..035092ecb72b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -53,7 +52,6 @@ public void SetUpSteps() (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()) }, Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index fac47deec9e3..c14ef5aaebb8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -72,21 +72,13 @@ public void SetUpSteps() AddStep("Create composer", () => { - Child = new DependencyProvidingContainer + Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] + Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) { - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()) - }, - Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) - { - Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) - { - // force the composer to fully overlap the playfield area by setting a 4:3 aspect ratio. - FillMode = FillMode.Fit, - FillAspectRatio = 4 / 3f - } + // force the composer to fully overlap the playfield area by setting a 4:3 aspect ratio. + FillMode = FillMode.Fit, + FillAspectRatio = 4 / 3f } }; }); From 59ff549b4d886e07fadc194c357a7313570f8c1b Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 16:46:56 +0800 Subject: [PATCH 150/579] Remove unused using --- osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index c14ef5aaebb8..f392841ac739 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -13,7 +12,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; From 251d00939439c5a27a91c1b7508a1fbc057f915c Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 5 Aug 2024 16:08:30 +0300 Subject: [PATCH 151/579] moved conversion formulas to respective classes --- .../Difficulty/OsuDifficultyCalculator.cs | 6 +++--- .../Difficulty/OsuPerformanceCalculator.cs | 7 ++++--- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 ++ osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 ++ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 007cd977e599..e93475ecff04 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -61,12 +61,12 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat flashlightRating *= 0.7; } - double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; - double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; + double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating); + double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); double baseFlashlightPerformance = 0.0; if (mods.Any(h => h is OsuModFlashlight)) - baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; + baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); double basePerformance = Math.Pow( diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 18a4b8be0caf..67d88b6b01ed 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -86,7 +87,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty); double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -139,7 +140,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib if (score.Mods.Any(h => h is OsuModRelax)) return 0.0; - double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -226,7 +227,7 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; - double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; + double flashlightValue = Flashlight.DifficultyToPerformance(attributes.FlashlightDifficulty); // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3d6d3f99c174..939641cae966 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -42,5 +42,7 @@ protected override double StrainValueAt(DifficultyHitObject current) } public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER; + + public static double DifficultyToPerformance(double difficulty) => 25 * Math.Pow(difficulty, 2); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 4a6328010b33..c4068ef0d72f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -67,5 +67,7 @@ public override double DifficultyValue() return difficulty * DifficultyMultiplier; } + + public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0; } } From ac57cdd1b32cde5b2ce156101ac178b7d2ef0fb9 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 5 Aug 2024 16:50:06 +0300 Subject: [PATCH 152/579] speed eval refactoring --- .../Difficulty/Evaluators/SpeedEvaluator.cs | 18 ++++++++++++++---- .../Difficulty/Skills/Speed.cs | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index 2df383aaa891..ae7a2542bf70 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class SpeedEvaluator { - private const double single_spacing_threshold = 125; + private const double single_spacing_threshold = 125; // 1.25 circles distance between centers private const double min_speed_bonus = 75; // ~200BPM private const double speed_balancing_factor = 40; @@ -50,16 +50,26 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindowGreat) / 0.93, 0.92, 1); - // derive speedBonus for calculation + // speedBonus will be 1.0 for BPM < 200 double speedBonus = 1.0; + // Add additional scaling bonus for streams/bursts higher than 200bpm if (strainTime < min_speed_bonus) speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); double travelDistance = osuPrevObj?.TravelDistance ?? 0; - double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance); + double distance = travelDistance + osuCurrObj.MinimumJumpDistance; - return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) * doubletapness / strainTime; + // Cap distance at single_spacing_threshold + distance = Math.Min(distance, single_spacing_threshold); + + double distanceBonus = 1 + Math.Pow(distance / single_spacing_threshold, 3.5); + + // Base difficulty with all bonuses + double difficulty = speedBonus * distanceBonus * 1000 / strainTime; + + // Apply penalty if there's doubletappable doubles + return difficulty * doubletapness; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 40aac013ab8a..f7f081b7eac3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - private double skillMultiplier => 1375; + private double skillMultiplier => 1.375; private double strainDecayBase => 0.3; private double currentStrain; From ace1a572429216c7d1e033d33167ceee6248751b Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 5 Aug 2024 16:53:06 +0300 Subject: [PATCH 153/579] Update SpeedEvaluator.cs --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index ae7a2542bf70..37fd11391c19 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -63,6 +63,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) // Cap distance at single_spacing_threshold distance = Math.Min(distance, single_spacing_threshold); + // Max distance bonus is 2 at single_spacing_threshold double distanceBonus = 1 + Math.Pow(distance / single_spacing_threshold, 3.5); // Base difficulty with all bonuses From 54a8f5b3064499c134fa17734dddaf629ed3628d Mon Sep 17 00:00:00 2001 From: kstefanowicz Date: Mon, 5 Aug 2024 11:06:27 -0400 Subject: [PATCH 154/579] Shorten TranslatableString --- osu.Game/Localisation/ChatStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index f7a36d9570e1..4661f9a53e81 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -27,7 +27,7 @@ public static class ChatStrings /// /// "press enter to type message..." /// - public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to type message..."); + public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to chat..."); private static string getKey(string key) => $"{prefix}:{key}"; } From 22ab6f577cae8120a59d4b05e5dea591eba067dc Mon Sep 17 00:00:00 2001 From: jkh675 Date: Tue, 6 Aug 2024 12:37:46 +0800 Subject: [PATCH 155/579] Add back the sample into `OsuContextMenu` --- osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index c9d89a920675..d15b1c2ee9d1 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -17,6 +17,11 @@ public partial class OsuContextMenuContainer : ContextMenuContainer protected override Menu CreateMenu() => menu = new OsuContextMenu(true); + public OsuContextMenuContainer() + { + AddInternal(samples); + } + public void CloseMenu() { menu.Close(); From cb877b76756a8e6c87f97def38673e43a1048b78 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Tue, 6 Aug 2024 13:09:48 +0800 Subject: [PATCH 156/579] Close the menu when selecting other object --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 21cd2e891f5f..63112edf3099 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -61,7 +61,7 @@ public abstract partial class SelectionHandler : CompositeDrawable, IKeyBindi public SelectionScaleHandler ScaleHandler { get; private set; } [Resolved(CanBeNull = true)] - protected OsuContextMenuContainer ContextMenuContainer { get; private set; } + protected OsuContextMenuContainer? ContextMenuContainer { get; private set; } protected SelectionHandler() { @@ -251,6 +251,8 @@ internal virtual void HandleSelected(SelectionBlueprint blueprint) SelectedItems.Add(blueprint.Item); selectedBlueprints.Add(blueprint); + + ContextMenuContainer?.CloseMenu(); } /// From c4572ec265eb03217430d899515ecdacce4fcb46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 15:17:43 +0900 Subject: [PATCH 157/579] Sanitise font sizes / weights --- osu.Game/Skinning/LegacyKeyCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 8a182de9b78a..609e21b9ffb3 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -63,7 +63,7 @@ public LegacyKeyCounter(InputTrigger trigger) Origin = Anchor.Centre, Text = trigger.Name, Colour = textColour, - Font = OsuFont.GetFont(size: 20), + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), }, }, } @@ -88,7 +88,7 @@ protected override void Activate(bool forwardPlayback = true) keyContainer.ScaleTo(0.75f, transition_duration, Easing.Out); keySprite.Colour = ActiveColour; overlayKeyText.Text = CountPresses.Value.ToString(); - overlayKeyText.Font = overlayKeyText.Font.With(weight: FontWeight.Bold); + overlayKeyText.Font = overlayKeyText.Font.With(weight: FontWeight.SemiBold); } protected override void Deactivate(bool forwardPlayback = true) From b91461e661798731b5cc6a59a4fe5be6365451f5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Aug 2024 15:17:52 +0900 Subject: [PATCH 158/579] Refactor + CI fixes --- osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs | 4 ++-- .../Edit/Compose/Components/SelectionHandler.cs | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index d15b1c2ee9d1..33758c618e6b 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -15,13 +15,13 @@ public partial class OsuContextMenuContainer : ContextMenuContainer private OsuContextMenu menu = null!; - protected override Menu CreateMenu() => menu = new OsuContextMenu(true); - public OsuContextMenuContainer() { AddInternal(samples); } + protected override Menu CreateMenu() => menu = new OsuContextMenu(true); + public void CloseMenu() { menu.Close(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 63112edf3099..d3461038bfa3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.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; @@ -51,14 +49,14 @@ public abstract partial class SelectionHandler : CompositeDrawable, IKeyBindi private readonly List> selectedBlueprints; - protected SelectionBox SelectionBox { get; private set; } + protected SelectionBox SelectionBox { get; private set; } = null!; [Resolved(CanBeNull = true)] - protected IEditorChangeHandler ChangeHandler { get; private set; } + protected IEditorChangeHandler? ChangeHandler { get; private set; } - public SelectionRotationHandler RotationHandler { get; private set; } + public SelectionRotationHandler RotationHandler { get; private set; } = null!; - public SelectionScaleHandler ScaleHandler { get; private set; } + public SelectionScaleHandler ScaleHandler { get; private set; } = null!; [Resolved(CanBeNull = true)] protected OsuContextMenuContainer? ContextMenuContainer { get; private set; } From 90395aea13cdbf34c63f330961e004cea2c953e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 15:43:39 +0900 Subject: [PATCH 159/579] Fix incorrect colour fallback handling Adds a note about `GetConfig` being stupid. --- osu.Game/Skinning/ISkin.cs | 3 +++ osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index fa04dda202b9..2af1eb8dd87c 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -47,6 +47,9 @@ public interface ISkin /// /// Retrieve a configuration value. /// + /// + /// Note that while this returns a bindable value, it is not actually updated. + /// Until the API is fixed, just use the received bindable's immediately. /// The requested configuration value. /// A matching value boxed in an , or null if unavailable. IBindable? GetConfig(TLookup lookup) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 8c652085e483..7e0317851d1b 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning { @@ -53,10 +54,8 @@ public LegacyKeyCounterDisplay() protected override void LoadComplete() { base.LoadComplete(); - source.GetConfig(SkinConfiguration.LegacySetting.InputOverlayText)?.BindValueChanged(v => - { - KeyTextColor = v.NewValue; - }, true); + + KeyTextColor = source.GetConfig(new SkinCustomColourLookup(SkinConfiguration.LegacySetting.InputOverlayText))?.Value ?? Color4.Black; Texture? backgroundTexture = source.GetTexture(@"inputoverlay-background"); From c574551ee0c645eecc0585677ce9b0ec172cee66 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Aug 2024 16:02:36 +0900 Subject: [PATCH 160/579] Simplify caching --- .../Visual/Editing/TimelineTestScene.cs | 8 +- .../Cursor/OsuContextMenuContainer.cs | 1 + osu.Game/Overlays/SkinEditor/SkinEditor.cs | 168 ++++++++-------- osu.Game/Screens/Edit/Editor.cs | 184 +++++++++--------- 4 files changed, 179 insertions(+), 182 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 2323612e89bd..cb45ad5a076e 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -51,7 +51,7 @@ protected override void LoadComplete() Composer.Alpha = 0; - var contextMenuContainer = new OsuContextMenuContainer + Add(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -75,11 +75,7 @@ protected override void LoadComplete() Origin = Anchor.Centre, } } - }; - - Dependencies.Cache(contextMenuContainer); - - Add(contextMenuContainer); + }); } [SetUpSteps] diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index 33758c618e6b..7b21a413f7a2 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -8,6 +8,7 @@ namespace osu.Game.Graphics.Cursor { + [Cached(typeof(OsuContextMenuContainer))] public partial class OsuContextMenuContainer : ContextMenuContainer { [Cached] diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index bb2d93f887d1..6ebc52c6b994 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -101,12 +101,6 @@ public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBind [Resolved] private IDialogOverlay? dialogOverlay { get; set; } - [Cached] - public OsuContextMenuContainer ContextMenuContainer { get; private set; } = new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - }; - public SkinEditor() { } @@ -121,110 +115,112 @@ private void load(EditorClipboard clipboard) { RelativeSizeAxes = Axes.Both; - ContextMenuContainer.Child = new GridContainer + AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - Content = new[] + Child = new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new Container + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - Name = @"Menu container", - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = MENU_HEIGHT, - Children = new Drawable[] + new Container { - new EditorMenuBar + Name = @"Menu container", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = MENU_HEIGHT, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] + new EditorMenuBar { - new MenuItem(CommonStrings.MenuBarFile) + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] { - Items = new OsuMenuItem[] + new MenuItem(CommonStrings.MenuBarFile) { - new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), - new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), + Items = new OsuMenuItem[] + { + new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), + new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), + }, }, - }, - new MenuItem(CommonStrings.MenuBarEdit) - { - Items = new OsuMenuItem[] + new MenuItem(CommonStrings.MenuBarEdit) { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - } - }, - headerText = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopRight, - Padding = new MarginPadding(5), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, + Items = new OsuMenuItem[] + { + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, + } + }, + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, }, }, }, - }, - new Drawable[] - { - new SkinEditorSceneLibrary - { - RelativeSizeAxes = Axes.X, - }, - }, - new Drawable[] - { - new GridContainer + new Drawable[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new SkinEditorSceneLibrary { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), + RelativeSizeAxes = Axes.X, }, - Content = new[] + }, + new Drawable[] + { + new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] { - componentsSidebar = new EditorSidebar(), - content = new Container + new Drawable[] { - Depth = float.MaxValue, - RelativeSizeAxes = Axes.Both, - }, - settingsSidebar = new EditorSidebar(), + componentsSidebar = new EditorSidebar(), + content = new Container + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, + settingsSidebar = new EditorSidebar(), + } } } - } - }, + }, + } } - }; - - AddInternal(ContextMenuContainer); + }); clipboardContent = clipboard.Content.GetBoundCopy(); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b1a066afb774..71d4693ac603 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework; @@ -76,12 +77,6 @@ public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler public const float WAVEFORM_VISUAL_OFFSET = 20; - [Cached] - public OsuContextMenuContainer ContextMenuContainer { get; private set; } = new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both - }; - public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; @@ -165,7 +160,7 @@ public bool ReadyForUse private string lastSavedHash; - private Container screenContainer; + private ScreenContainer screenContainer; [CanBeNull] private readonly EditorLoader loader; @@ -325,108 +320,110 @@ private void load(OsuConfigManager config) editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - ContextMenuContainer.AddRange(new Drawable[] + AddInternal(new OsuContextMenuContainer { - new Container + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Name = "Screen container", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 50 }, - Child = screenContainer = new Container + new Container { + Name = "Screen container", RelativeSizeAxes = Axes.Both, - } - }, - new Container - { - Name = "Top bar", - RelativeSizeAxes = Axes.X, - Height = 40, - Children = new Drawable[] - { - new EditorMenuBar + Padding = new MarginPadding { Top = 40, Bottom = 50 }, + Child = screenContainer = new ScreenContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - MaxHeight = 600, - Items = new[] + } + }, + new Container + { + Name = "Top bar", + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] + { + new EditorMenuBar { - new MenuItem(CommonStrings.MenuBarFile) - { - Items = createFileMenuItems().ToList() - }, - new MenuItem(CommonStrings.MenuBarEdit) + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + MaxHeight = 600, + Items = new[] { - Items = new[] + new MenuItem(CommonStrings.MenuBarFile) { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - new MenuItem(CommonStrings.MenuBarView) - { - Items = new[] + Items = createFileMenuItems().ToList() + }, + new MenuItem(CommonStrings.MenuBarEdit) { - new MenuItem(EditorStrings.Timeline) - { - Items = - [ - new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), - new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) - { - State = { BindTarget = editorTimelineShowTimingChanges } - }, - new ToggleMenuItem(EditorStrings.TimelineShowTicks) - { - State = { BindTarget = editorTimelineShowTicks } - }, - ] - }, - new BackgroundDimMenuItem(editorBackgroundDim), - new ToggleMenuItem(EditorStrings.ShowHitMarkers) - { - State = { BindTarget = editorHitMarkers }, - }, - new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) + Items = new[] { - State = { BindTarget = editorAutoSeekOnPlacement }, - }, - new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, + new MenuItem(CommonStrings.MenuBarView) + { + Items = new[] { - State = { BindTarget = editorLimitedDistanceSnap }, + new MenuItem(EditorStrings.Timeline) + { + Items = + [ + new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) + { + State = { BindTarget = editorTimelineShowTimingChanges } + }, + new ToggleMenuItem(EditorStrings.TimelineShowTicks) + { + State = { BindTarget = editorTimelineShowTicks } + }, + ] + }, + new BackgroundDimMenuItem(editorBackgroundDim), + new ToggleMenuItem(EditorStrings.ShowHitMarkers) + { + State = { BindTarget = editorHitMarkers }, + }, + new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) + { + State = { BindTarget = editorAutoSeekOnPlacement }, + }, + new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) + { + State = { BindTarget = editorLimitedDistanceSnap }, + } } - } - }, - new MenuItem(EditorStrings.Timing) - { - Items = new MenuItem[] + }, + new MenuItem(EditorStrings.Timing) { - new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) + Items = new MenuItem[] + { + new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) + } } } - } - }, - screenSwitcher = new EditorScreenSwitcherControl - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - X = -10, - Current = Mode, + }, + screenSwitcher = new EditorScreenSwitcherControl + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -10, + Current = Mode, + }, }, }, - }, - bottomBar = new BottomBar(), - MutationTracker, + bottomBar = new BottomBar(), + MutationTracker, + } }); - AddInternal(ContextMenuContainer); - changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); @@ -1012,7 +1009,7 @@ private void onModeChanged(ValueChangedEvent e) throw new InvalidOperationException("Editor menu bar switched to an unsupported mode"); } - LoadComponentAsync(currentScreen, newScreen => + screenContainer.LoadComponentAsync(currentScreen, newScreen => { if (newScreen == currentScreen) { @@ -1390,5 +1387,12 @@ public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) { } } + + private partial class ScreenContainer : Container + { + public new Task LoadComponentAsync([NotNull] TLoadable component, Action onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null) + where TLoadable : Drawable + => base.LoadComponentAsync(component, onLoaded, cancellation, scheduler); + } } } From c26a664b849483d9dc12867cf3bcfa2f0fefd829 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Aug 2024 16:08:42 +0900 Subject: [PATCH 161/579] Use InternalChild directly --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 6ebc52c6b994..a6bb8694abf3 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -115,7 +115,7 @@ private void load(EditorClipboard clipboard) { RelativeSizeAxes = Axes.Both; - AddInternal(new OsuContextMenuContainer + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Child = new GridContainer @@ -220,7 +220,7 @@ private void load(EditorClipboard clipboard) }, } } - }); + }; clipboardContent = clipboard.Content.GetBoundCopy(); } From 41d84ea56b1262f356b8c1d657ad8ae211d5ee77 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Aug 2024 16:11:29 +0900 Subject: [PATCH 162/579] Revert all SkinEditor changes (none required) --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index a6bb8694abf3..484af34603ff 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -23,6 +23,7 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Dialog; @@ -32,7 +33,6 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; -using osu.Game.Graphics.Cursor; namespace osu.Game.Overlays.SkinEditor { @@ -127,6 +127,7 @@ private void load(EditorClipboard clipboard) new Dimension(GridSizeMode.AutoSize), new Dimension(), }, + Content = new[] { new Drawable[] From 8619bbb9435c82018ffc50ab15a61430ae77146a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 15:52:03 +0900 Subject: [PATCH 163/579] Fix legacy key counter's background being visible when intended to be hidden --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 11 +++--- .../Screens/Play/ArgonKeyCounterDisplay.cs | 3 +- .../Play/HUD/DefaultKeyCounterDisplay.cs | 3 +- .../Screens/Play/HUD/KeyCounterDisplay.cs | 35 ++++++++++++------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 2 +- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index f8226eb21de7..16b2a54a4546 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -45,7 +44,7 @@ public partial class TestSceneHUDOverlay : OsuManualInputManagerTestScene // best way to check without exposing. private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); - private Drawable keyCounterFlow => hudOverlay.ChildrenOfType().First().ChildrenOfType>().Single(); + private Drawable keyCounterContent => hudOverlay.ChildrenOfType().First().ChildrenOfType().Skip(1).First(); public TestSceneHUDOverlay() { @@ -79,7 +78,7 @@ public void TestShownByDefault() AddAssert("showhud is set", () => hudOverlay.ShowHud.Value); AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0)); - AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent); + AddAssert("key counter flow is visible", () => keyCounterContent.IsPresent); AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent); } @@ -104,7 +103,7 @@ public void TestHideExternally() AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. - AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); + AddAssert("key counter flow not affected", () => keyCounterContent.IsPresent); } [Test] @@ -150,11 +149,11 @@ public void TestChangeHUDVisibilityOnHiddenKeyCounter() AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); - AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent); + AddUntilStep("key counters hidden", () => !keyCounterContent.IsPresent); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0)); - AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent); + AddUntilStep("key counters still hidden", () => !keyCounterContent.IsPresent); } [Test] diff --git a/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs b/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs index 44b90fcad0d7..d5044b9f06b9 100644 --- a/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs @@ -14,11 +14,10 @@ public partial class ArgonKeyCounterDisplay : KeyCounterDisplay public ArgonKeyCounterDisplay() { - InternalChild = KeyFlow = new FillFlowContainer + Child = KeyFlow = new FillFlowContainer { Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Alpha = 0, Spacing = new Vector2(2), }; } diff --git a/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs index e0f96d32bc4f..dfb547453e82 100644 --- a/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs @@ -16,11 +16,10 @@ public partial class DefaultKeyCounterDisplay : KeyCounterDisplay public DefaultKeyCounterDisplay() { - InternalChild = KeyFlow = new FillFlowContainer + Child = KeyFlow = new FillFlowContainer { Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Alpha = 0, }; } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index 0a5d6b763e14..a1e90687a887 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play.HUD /// /// A flowing display of all gameplay keys. Individual keys can be added using implementations. /// - public abstract partial class KeyCounterDisplay : CompositeDrawable, ISerialisableDrawable + public abstract partial class KeyCounterDisplay : Container, ISerialisableDrawable { /// /// Whether the key counter should be visible regardless of the configuration value. @@ -29,25 +29,22 @@ public abstract partial class KeyCounterDisplay : CompositeDrawable, ISerialisab private readonly IBindableList triggers = new BindableList(); + protected override Container Content { get; } = new Container + { + Alpha = 0, + AutoSizeAxes = Axes.Both, + }; + [Resolved] private InputCountController controller { get; set; } = null!; private const int duration = 100; - protected void UpdateVisibility() + protected KeyCounterDisplay() { - bool visible = AlwaysVisible.Value || ConfigVisibility.Value; - - // Isolate changing visibility of the key counters from fading this component. - KeyFlow.FadeTo(visible ? 1 : 0, duration); - - // Ensure a valid size is immediately obtained even if partially off-screen - // See https://github.com/ppy/osu/issues/14793. - KeyFlow.AlwaysPresent = visible; + AddInternal(Content); } - protected abstract KeyCounter CreateCounter(InputTrigger trigger); - [BackgroundDependencyLoader] private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset) { @@ -70,6 +67,20 @@ protected override void LoadComplete() ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true); } + protected void UpdateVisibility() + { + bool visible = AlwaysVisible.Value || ConfigVisibility.Value; + + // Isolate changing visibility of the key counters from fading this component. + Content.FadeTo(visible ? 1 : 0, duration); + + // Ensure a valid size is immediately obtained even if partially off-screen + // See https://github.com/ppy/osu/issues/14793. + Content.AlwaysPresent = visible; + } + + protected abstract KeyCounter CreateCounter(InputTrigger trigger); + private void triggersChanged(object? sender, NotifyCollectionChangedEventArgs e) { KeyFlow.Clear(); diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 7e0317851d1b..fdbd3570f51b 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -26,7 +26,7 @@ public LegacyKeyCounterDisplay() { AutoSizeAxes = Axes.Both; - AddRangeInternal(new Drawable[] + AddRange(new Drawable[] { backgroundSprite = new Sprite { From aae49d362f5551ceab9ef032e5c1f67a887486e3 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Tue, 6 Aug 2024 16:34:36 +0800 Subject: [PATCH 164/579] Fix unit test code quality --- .../Editor/TestSceneObjectMerging.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index dfe950c01e0f..fd711e543cf8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -34,7 +34,7 @@ public void TestSimpleMerge() }); moveMouseToHitObject(1); - AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true); + AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection")); mergeSelection(); @@ -198,7 +198,7 @@ public void TestIllegalMerge() }); moveMouseToHitObject(1); - AddAssert("merge option not available", () => selectionHandler.ContextMenuItems?.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection")); + AddAssert("merge option not available", () => selectionHandler.ContextMenuItems.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection")); mergeSelection(); AddAssert("circles not merged", () => circle1 is not null && circle2 is not null && EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2)); @@ -222,7 +222,7 @@ public void TestSameStartTimeMerge() }); moveMouseToHitObject(1); - AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true); + AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection")); mergeSelection(); From 725dc4de9b80773fe057059eacab9ae244ed21ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 18:17:21 +0900 Subject: [PATCH 165/579] Use transformers for per-skin key counter implementation --- .../Legacy/CatchLegacySkinTransformer.cs | 115 +++++---- .../Legacy/OsuLegacySkinTransformer.cs | 230 ++++++++++-------- osu.Game/Skinning/LegacySkin.cs | 128 ++++------ 3 files changed, 252 insertions(+), 221 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index d1ef47cf17a1..b102ca990cd9 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Skinning.Legacy @@ -28,11 +29,15 @@ public CatchLegacySkinTransformer(ISkin skin) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is SkinComponentsContainerLookup containerLookup) + switch (lookup) { - switch (containerLookup.Target) - { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case SkinComponentsContainerLookup containerLookup: + if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) + return base.GetDrawableComponent(lookup); + + // Modifications for global components. + if (containerLookup.Ruleset == null) + { var components = base.GetDrawableComponent(lookup) as Container; if (providesComboCounter && components != null) @@ -44,60 +49,84 @@ public CatchLegacySkinTransformer(ISkin skin) } return components; - } - } + } - if (lookup is CatchSkinComponentLookup catchSkinComponent) - { - switch (catchSkinComponent.Component) - { - case CatchSkinComponents.Fruit: - if (hasPear) - return new LegacyFruitPiece(); + // Skin has configuration. + if (base.GetDrawableComponent(lookup) is Drawable d) + return d; - return null; + // Our own ruleset components default. + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); - case CatchSkinComponents.Banana: - if (GetTexture("fruit-bananas") != null) - return new LegacyBananaPiece(); + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + }) + { + Children = new Drawable[] + { + new LegacyKeyCounterDisplay(), + } + }; - return null; + case CatchSkinComponentLookup catchSkinComponent: + switch (catchSkinComponent.Component) + { + case CatchSkinComponents.Fruit: + if (hasPear) + return new LegacyFruitPiece(); - case CatchSkinComponents.Droplet: - if (GetTexture("fruit-drop") != null) - return new LegacyDropletPiece(); + return null; - return null; + case CatchSkinComponents.Banana: + if (GetTexture("fruit-bananas") != null) + return new LegacyBananaPiece(); - case CatchSkinComponents.Catcher: - decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; + return null; - if (version < 2.3m) - { - if (hasOldStyleCatcherSprite()) - return new LegacyCatcherOld(); - } + case CatchSkinComponents.Droplet: + if (GetTexture("fruit-drop") != null) + return new LegacyDropletPiece(); - if (hasNewStyleCatcherSprite()) - return new LegacyCatcherNew(); + return null; - return null; + case CatchSkinComponents.Catcher: + decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; - case CatchSkinComponents.CatchComboCounter: - if (providesComboCounter) - return new LegacyCatchComboCounter(); + if (version < 2.3m) + { + if (hasOldStyleCatcherSprite()) + return new LegacyCatcherOld(); + } - return null; + if (hasNewStyleCatcherSprite()) + return new LegacyCatcherNew(); - case CatchSkinComponents.HitExplosion: - if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite()) - return new LegacyHitExplosion(); + return null; - return null; + case CatchSkinComponents.CatchComboCounter: + if (providesComboCounter) + return new LegacyCatchComboCounter(); + + return null; + + case CatchSkinComponents.HitExplosion: + if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite()) + return new LegacyHitExplosion(); + + return null; - default: - throw new UnsupportedSkinComponentException(lookup); - } + default: + throw new UnsupportedSkinComponentException(lookup); + } } return base.GetDrawableComponent(lookup); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d2ebc68c5225..2c2f228fae4b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; @@ -41,139 +42,178 @@ public OsuLegacySkinTransformer(ISkin skin) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is OsuSkinComponentLookup osuComponent) + switch (lookup) { - switch (osuComponent.Component) - { - case OsuSkinComponents.FollowPoint: - return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS)); + case SkinComponentsContainerLookup containerLookup: + // Only handle per ruleset defaults here. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); - case OsuSkinComponents.SliderScorePoint: - return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS); + // Skin has configuration. + if (base.GetDrawableComponent(lookup) is Drawable d) + return d; - case OsuSkinComponents.SliderFollowCircle: - var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: MAX_FOLLOW_CIRCLE_AREA_SIZE); - if (followCircleContent != null) - return new LegacyFollowCircle(followCircleContent); + // Our own ruleset components default. + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); + + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + }) + { + Children = new Drawable[] + { + new LegacyKeyCounterDisplay(), + } + }; + } - return null; + return null; - case OsuSkinComponents.SliderBall: - if (GetTexture("sliderb") != null || GetTexture("sliderb0") != null) - return new LegacySliderBall(this); + case OsuSkinComponentLookup osuComponent: + switch (osuComponent.Component) + { + case OsuSkinComponents.FollowPoint: + return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS)); - return null; + case OsuSkinComponents.SliderScorePoint: + return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS); - case OsuSkinComponents.SliderBody: - if (hasHitCircle.Value) - return new LegacySliderBody(); + case OsuSkinComponents.SliderFollowCircle: + var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: MAX_FOLLOW_CIRCLE_AREA_SIZE); + if (followCircleContent != null) + return new LegacyFollowCircle(followCircleContent); - return null; + return null; - case OsuSkinComponents.SliderTailHitCircle: - if (hasHitCircle.Value) - return new LegacyMainCirclePiece("sliderendcircle", false); + case OsuSkinComponents.SliderBall: + if (GetTexture("sliderb") != null || GetTexture("sliderb0") != null) + return new LegacySliderBall(this); - return null; + return null; - case OsuSkinComponents.SliderHeadHitCircle: - if (hasHitCircle.Value) - return new LegacySliderHeadHitCircle(); + case OsuSkinComponents.SliderBody: + if (hasHitCircle.Value) + return new LegacySliderBody(); - return null; + return null; - case OsuSkinComponents.ReverseArrow: - if (hasHitCircle.Value) - return new LegacyReverseArrow(); + case OsuSkinComponents.SliderTailHitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece("sliderendcircle", false); - return null; + return null; - case OsuSkinComponents.HitCircle: - if (hasHitCircle.Value) - return new LegacyMainCirclePiece(); + case OsuSkinComponents.SliderHeadHitCircle: + if (hasHitCircle.Value) + return new LegacySliderHeadHitCircle(); - return null; + return null; - case OsuSkinComponents.Cursor: - if (GetTexture("cursor") != null) - return new LegacyCursor(this); + case OsuSkinComponents.ReverseArrow: + if (hasHitCircle.Value) + return new LegacyReverseArrow(); - return null; + return null; - case OsuSkinComponents.CursorTrail: - if (GetTexture("cursortrail") != null) - return new LegacyCursorTrail(this); + case OsuSkinComponents.HitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece(); - return null; + return null; - case OsuSkinComponents.CursorRipple: - if (GetTexture("cursor-ripple") != null) - { - var ripple = this.GetAnimation("cursor-ripple", false, false); + case OsuSkinComponents.Cursor: + if (GetTexture("cursor") != null) + return new LegacyCursor(this); - // In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible. - // If anyone complains about these not being applied, this can be uncommented. - // - // But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size, - // so we might be okay. - // - // if (ripple != null) - // { - // ripple.Scale = new Vector2(0.5f); - // ripple.Alpha = 0.2f; - // } + return null; - return ripple; - } + case OsuSkinComponents.CursorTrail: + if (GetTexture("cursortrail") != null) + return new LegacyCursorTrail(this); - return null; + return null; - case OsuSkinComponents.CursorParticles: - if (GetTexture("star2") != null) - return new LegacyCursorParticles(); + case OsuSkinComponents.CursorRipple: + if (GetTexture("cursor-ripple") != null) + { + var ripple = this.GetAnimation("cursor-ripple", false, false); + + // In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible. + // If anyone complains about these not being applied, this can be uncommented. + // + // But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size, + // so we might be okay. + // + // if (ripple != null) + // { + // ripple.Scale = new Vector2(0.5f); + // ripple.Alpha = 0.2f; + // } + + return ripple; + } - return null; + return null; + + case OsuSkinComponents.CursorParticles: + if (GetTexture("star2") != null) + return new LegacyCursorParticles(); - case OsuSkinComponents.CursorSmoke: - if (GetTexture("cursor-smoke") != null) - return new LegacySmokeSegment(); + return null; - return null; + case OsuSkinComponents.CursorSmoke: + if (GetTexture("cursor-smoke") != null) + return new LegacySmokeSegment(); - case OsuSkinComponents.HitCircleText: - if (!this.HasFont(LegacyFont.HitCircle)) return null; - const float hitcircle_text_scale = 0.8f; - return new LegacySpriteText(LegacyFont.HitCircle) - { - // stable applies a blanket 0.8x scale to hitcircle fonts - Scale = new Vector2(hitcircle_text_scale), - MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale, - }; + case OsuSkinComponents.HitCircleText: + if (!this.HasFont(LegacyFont.HitCircle)) + return null; - case OsuSkinComponents.SpinnerBody: - bool hasBackground = GetTexture("spinner-background") != null; + const float hitcircle_text_scale = 0.8f; + return new LegacySpriteText(LegacyFont.HitCircle) + { + // stable applies a blanket 0.8x scale to hitcircle fonts + Scale = new Vector2(hitcircle_text_scale), + MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale, + }; - if (GetTexture("spinner-top") != null && !hasBackground) - return new LegacyNewStyleSpinner(); - else if (hasBackground) - return new LegacyOldStyleSpinner(); + case OsuSkinComponents.SpinnerBody: + bool hasBackground = GetTexture("spinner-background") != null; - return null; + if (GetTexture("spinner-top") != null && !hasBackground) + return new LegacyNewStyleSpinner(); + else if (hasBackground) + return new LegacyOldStyleSpinner(); - case OsuSkinComponents.ApproachCircle: - if (GetTexture(@"approachcircle") != null) - return new LegacyApproachCircle(); + return null; - return null; + case OsuSkinComponents.ApproachCircle: + if (GetTexture(@"approachcircle") != null) + return new LegacyApproachCircle(); - default: - throw new UnsupportedSkinComponentException(lookup); - } - } + return null; - return base.GetDrawableComponent(lookup); + default: + throw new UnsupportedSkinComponentException(lookup); + } + + default: + return base.GetDrawableComponent(lookup); + } } public override IBindable? GetConfig(TLookup lookup) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 4ca0e3cac0b1..b1b171eef907 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -23,7 +23,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; -using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -356,16 +355,57 @@ protected override void ParseConfigurationStream(Stream stream) switch (lookup) { case SkinComponentsContainerLookup containerLookup: + // Only handle global level defaults for now. + if (containerLookup.Ruleset != null) + return null; switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - return createDefaultHUDComponents(containerLookup); - - default: - return null; + return new DefaultSkinComponentsContainer(container => + { + var score = container.OfType().FirstOrDefault(); + var accuracy = container.OfType().FirstOrDefault(); + + if (score != null && accuracy != null) + { + accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; + } + + var songProgress = container.OfType().FirstOrDefault(); + + if (songProgress != null && accuracy != null) + { + songProgress.Anchor = Anchor.TopRight; + songProgress.Origin = Anchor.CentreRight; + songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18; + songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); + } + + var hitError = container.OfType().FirstOrDefault(); + + if (hitError != null) + { + hitError.Anchor = Anchor.BottomCentre; + hitError.Origin = Anchor.CentreLeft; + hitError.Rotation = -90; + } + }) + { + Children = new Drawable[] + { + new LegacyComboCounter(), + new LegacyScoreCounter(), + new LegacyAccuracyCounter(), + new LegacySongProgress(), + new LegacyHealthDisplay(), + new BarHitErrorMeter(), + } + }; } + return null; + case GameplaySkinComponentLookup resultComponent: // kind of wasteful that we throw this away, but should do for now. @@ -388,84 +428,6 @@ protected override void ParseConfigurationStream(Stream stream) return null; } - private static DefaultSkinComponentsContainer? createDefaultHUDComponents(SkinComponentsContainerLookup containerLookup) - { - switch (containerLookup.Ruleset?.ShortName) - { - case null: - { - return new DefaultSkinComponentsContainer(container => - { - var score = container.OfType().FirstOrDefault(); - var accuracy = container.OfType().FirstOrDefault(); - - if (score != null && accuracy != null) - { - accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; - } - - var songProgress = container.OfType().FirstOrDefault(); - - if (songProgress != null && accuracy != null) - { - songProgress.Anchor = Anchor.TopRight; - songProgress.Origin = Anchor.CentreRight; - songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18; - songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); - } - - var hitError = container.OfType().FirstOrDefault(); - - if (hitError != null) - { - hitError.Anchor = Anchor.BottomCentre; - hitError.Origin = Anchor.CentreLeft; - hitError.Rotation = -90; - } - }) - { - Children = new Drawable[] - { - new LegacyComboCounter(), - new LegacyScoreCounter(), - new LegacyAccuracyCounter(), - new LegacySongProgress(), - new LegacyHealthDisplay(), - new BarHitErrorMeter(), - } - }; - } - - case @"osu": - case @"fruits": - { - return new DefaultSkinComponentsContainer(container => - { - var keyCounter = container.OfType().FirstOrDefault(); - - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - }) - { - Children = new Drawable[] - { - new LegacyKeyCounterDisplay(), - } - }; - } - - default: - return null; - } - } - private Texture? getParticleTexture(HitResult result) { switch (result) From f7b45a26defef5a47f3a0ebf6f91acff623661da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 19:25:23 +0900 Subject: [PATCH 166/579] Improve test coverage and segregation --- .../TestSceneModCustomisationPanel.cs | 93 ++++++++++++------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 16c9c2bc14c1..1ada5f40ab09 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { @@ -27,6 +28,9 @@ public partial class TestSceneModCustomisationPanel : OsuManualInputManagerTestS [SetUp] public void SetUp() => Schedule(() => { + SelectedMods.Value = Array.Empty(); + InputManager.MoveMouseTo(Vector2.One); + Child = new Container { RelativeSizeAxes = Axes.Both, @@ -71,66 +75,87 @@ public void TestDisplay() } [Test] - public void TestHoverExpand() + public void TestHoverDoesNotExpandWhenNoCustomisableMods() { - // Can not expand by hovering when no supported mod - { - AddStep("hover header", () => InputManager.MoveMouseTo(header)); + AddStep("hover header", () => InputManager.MoveMouseTo(header)); - AddAssert("not expanded", () => !panel.Expanded); + checkExpanded(false); - AddStep("hover content", () => InputManager.MoveMouseTo(content)); + AddStep("hover content", () => InputManager.MoveMouseTo(content)); - AddAssert("neither expanded", () => !panel.Expanded); + checkExpanded(false); - AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); - } + AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); + } + [Test] + public void TestHoverExpandsWithCustomisableMods() + { AddStep("add customisable mod", () => { SelectedMods.Value = new[] { new OsuModDoubleTime() }; panel.Enabled.Value = true; }); - // Can expand by hovering when supported mod - { - AddStep("hover header", () => InputManager.MoveMouseTo(header)); + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); - AddAssert("expanded", () => panel.Expanded); + AddStep("move to content", () => InputManager.MoveMouseTo(content)); + checkExpanded(true); - AddStep("hover content", () => InputManager.MoveMouseTo(content)); + AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One)); + checkExpanded(false); - AddAssert("still expanded", () => panel.Expanded); - } + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); - // Will collapse when mouse left from content - { - AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); - - AddAssert("not expanded", () => !panel.Expanded); - } + AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One)); + checkExpanded(false); + } - // Will collapse when mouse left from header + [Test] + public void TestExpandedStatePersistsWhenClicked() + { + AddStep("add customisable mod", () => { - AddStep("hover header", () => InputManager.MoveMouseTo(header)); + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + panel.Enabled.Value = true; + }); + + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); - AddAssert("expanded", () => panel.Expanded); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkExpanded(false); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkExpanded(true); - AddStep("left from header", () => InputManager.MoveMouseTo(Vector2.One)); + AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One)); + checkExpanded(true); - AddAssert("not expanded", () => !panel.Expanded); - } + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkExpanded(false); + } - // Not collapse when mouse left if not expanded by hovering + [Test] + public void TestHoverExpandsAndCollapsesWhenHeaderClicked() + { + AddStep("add customisable mod", () => { - AddStep("expand not by hovering", () => panel.Expanded = true); + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + panel.Enabled.Value = true; + }); - AddStep("hover content", () => InputManager.MoveMouseTo(content)); + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); - AddStep("moust left", () => InputManager.MoveMouseTo(Vector2.One)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkExpanded(false); + } - AddAssert("still expanded", () => panel.Expanded); - } + private void checkExpanded(bool expanded) + { + AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.Expanded, () => Is.EqualTo(expanded)); } } } From 1aea8e911cad4e86d7108fb8067b3ce30df84975 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 01:33:54 +0900 Subject: [PATCH 167/579] Add test coverage of chat mentions --- .../Visual/Online/TestSceneDrawableChannel.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index dd12ee34ede1..6a077708e325 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -33,6 +33,34 @@ public void SetUpSteps() }); } + [Test] + public void TestMention() + { + AddStep("add normal message", () => channel.AddNewMessages( + new Message(1) + { + Sender = new APIUser + { + Id = 2, + Username = "TestUser2" + }, + Content = "Hello how are you today?", + Timestamp = new DateTimeOffset(2021, 12, 11, 13, 33, 24, TimeSpan.Zero) + })); + + AddStep("add mention", () => channel.AddNewMessages( + new Message(2) + { + Sender = new APIUser + { + Id = 2, + Username = "TestUser2" + }, + Content = $"Hello {API.LocalUser.Value.Username} how are you today?", + Timestamp = new DateTimeOffset(2021, 12, 11, 13, 33, 25, TimeSpan.Zero) + })); + } + [Test] public void TestDaySeparators() { From a61bf670d8f2f716f1b8df0b02421f8f67aff886 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 01:18:45 +0900 Subject: [PATCH 168/579] Highlight mentions in chat --- osu.Game/Overlays/Chat/ChatLine.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 29c6ec2564a0..3f8862de3687 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osuTK; @@ -104,6 +105,8 @@ public bool AlternatingBackground } } + private bool isMention; + /// /// The colour used to paint the author's username. /// @@ -255,12 +258,21 @@ public void Highlight() private void styleMessageContent(SpriteText text) { text.Shadow = false; - text.Font = text.Font.With(size: FontSize, italics: Message.IsAction); + text.Font = text.Font.With(size: FontSize, italics: Message.IsAction, weight: isMention ? FontWeight.SemiBold : FontWeight.Medium); + + Color4 messageColour = colourProvider?.Content1 ?? Colour4.White; + + if (isMention) + messageColour = colourProvider?.Highlight1 ?? Color4.Orange; + else if (Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour)) + messageColour = Color4Extensions.FromHex(message.Sender.Colour); - bool messageHasColour = Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour); - text.Colour = messageHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : colourProvider?.Content1 ?? Colour4.White; + text.Colour = messageColour; } + [Resolved] + private IAPIProvider api { get; set; } = null!; + private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); @@ -280,6 +292,8 @@ private void updateMessageContent() // remove non-existent channels from the link list message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true); + isMention = MessageNotifier.CheckContainsUsername(message.DisplayContent, api.LocalUser.Value.Username); + drawableContentFlow.Clear(); drawableContentFlow.AddLinks(message.DisplayContent, message.Links); } From 06ff858256f1dac610e1daece956252d4a281d4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 14:40:52 +0900 Subject: [PATCH 169/579] Fix `PresentBeatmap` sometimes favouring an already `DeletePending` beatmap --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 53b2fd59042b..7e4d2ccf39b1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -642,10 +642,10 @@ public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate diffi Live databasedSet = null; if (beatmap.OnlineID > 0) - databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); + databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID && !s.DeletePending); if (beatmap is BeatmapSetInfo localBeatmap) - databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); + databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash && !s.DeletePending); if (databasedSet == null) { From 5a63c25f4956b042259e77a3a42d48103393201a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 14:42:34 +0900 Subject: [PATCH 170/579] Fix clicking the beatmap import notification at the daily challenge screen exiting to main menu --- .../DailyChallenge/DailyChallenge.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index da2d9036c59c..c1e1142625b3 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { [Cached(typeof(IPreviewTrackOwner))] - public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner + public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePresentBeatmap { private readonly Room room; private readonly PlaylistItem playlistItem; @@ -546,5 +546,23 @@ protected override void Dispose(bool isDisposing) if (metadataClient.IsNotNull()) metadataClient.MultiplayerRoomScoreSet -= onRoomScoreSet; } + + [Resolved] + private OsuGame? game { get; set; } + + public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) + { + if (!this.IsCurrentScreen()) + return; + + // We can only handle the current daily challenge beatmap. + // If the import was for a different beatmap, pass the duty off to global handling. + if (beatmap.BeatmapSetInfo.OnlineID != playlistItem.Beatmap.BeatmapSet!.OnlineID) + { + game?.PresentBeatmap(beatmap.BeatmapSetInfo, b => b.ID == beatmap.BeatmapInfo.ID); + } + + // And if we're handling, we don't really have much to do here. + } } } From fcede9abd786854bc0858fe90db2cbdf320a56ac Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Wed, 7 Aug 2024 03:34:07 -0400 Subject: [PATCH 171/579] Bump velopack version --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 7a2bb599fd1b..d86fcec3961e 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From dccf766ff3ba723e737257a0dabdeadc6496423f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 19:01:14 +0900 Subject: [PATCH 172/579] Remove obsoleted download setting --- osu.Game/Configuration/OsuConfigManager.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index bef1cf289915..86b8ba98c3bd 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -67,12 +67,6 @@ protected override void InitialiseDefaults() SetDefault(OsuSetting.Username, string.Empty); SetDefault(OsuSetting.Token, string.Empty); -#pragma warning disable CS0618 // Type or member is obsolete - // this default set MUST remain despite the setting being deprecated, because `SetDefault()` calls are implicitly used to declare the type returned for the lookup. - // if this is removed, the setting will be interpreted as a string, and `Migrate()` will fail due to cast failure. - // can be removed 20240618 - SetDefault(OsuSetting.AutomaticallyDownloadWhenSpectating, false); -#pragma warning restore CS0618 // Type or member is obsolete SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, false); SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled => @@ -244,12 +238,6 @@ public void Migrate() // migrations can be added here using a condition like: // if (combined < 20220103) { performMigration() } - if (combined < 20230918) - { -#pragma warning disable CS0618 // Type or member is obsolete - SetValue(OsuSetting.AutomaticallyDownloadMissingBeatmaps, Get(OsuSetting.AutomaticallyDownloadWhenSpectating)); // can be removed 20240618 -#pragma warning restore CS0618 // Type or member is obsolete - } } public override TrackedSettings CreateTrackedSettings() @@ -424,9 +412,6 @@ public enum OsuSetting EditorAutoSeekOnPlacement, DiscordRichPresence, - [Obsolete($"Use {nameof(AutomaticallyDownloadMissingBeatmaps)} instead.")] // can be removed 20240318 - AutomaticallyDownloadWhenSpectating, - ShowOnlineExplicitContent, LastProcessedMetadataId, SafeAreaConsiderations, From 227878b67adf0cdb9789e5f1080ad57e9e9cfad6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 19:01:47 +0900 Subject: [PATCH 173/579] Change default for "automatically download beatmaps" to enabled --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 86b8ba98c3bd..d00856dd8057 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -67,7 +67,7 @@ protected override void InitialiseDefaults() SetDefault(OsuSetting.Username, string.Empty); SetDefault(OsuSetting.Token, string.Empty); - SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, false); + SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, true); SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled => { From 43f1fe350d2874d3e2082db8b11805a21b313fce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 14:40:52 +0900 Subject: [PATCH 174/579] Fix `PresentBeatmap` sometimes favouring an already `DeletePending` beatmap --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 53b2fd59042b..7e4d2ccf39b1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -642,10 +642,10 @@ public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate diffi Live databasedSet = null; if (beatmap.OnlineID > 0) - databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); + databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID && !s.DeletePending); if (beatmap is BeatmapSetInfo localBeatmap) - databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); + databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash && !s.DeletePending); if (databasedSet == null) { From 3c05b975a08dacc644f9877f49f58e40bf92668b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 14:42:34 +0900 Subject: [PATCH 175/579] Fix clicking the beatmap import notification at the daily challenge screen exiting to main menu --- .../DailyChallenge/DailyChallenge.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index da2d9036c59c..c1e1142625b3 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { [Cached(typeof(IPreviewTrackOwner))] - public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner + public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePresentBeatmap { private readonly Room room; private readonly PlaylistItem playlistItem; @@ -546,5 +546,23 @@ protected override void Dispose(bool isDisposing) if (metadataClient.IsNotNull()) metadataClient.MultiplayerRoomScoreSet -= onRoomScoreSet; } + + [Resolved] + private OsuGame? game { get; set; } + + public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) + { + if (!this.IsCurrentScreen()) + return; + + // We can only handle the current daily challenge beatmap. + // If the import was for a different beatmap, pass the duty off to global handling. + if (beatmap.BeatmapSetInfo.OnlineID != playlistItem.Beatmap.BeatmapSet!.OnlineID) + { + game?.PresentBeatmap(beatmap.BeatmapSetInfo, b => b.ID == beatmap.BeatmapInfo.ID); + } + + // And if we're handling, we don't really have much to do here. + } } } From b081b4771457b901c8cbf31164619c7a211055a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 17:36:01 +0900 Subject: [PATCH 176/579] Add test of daily challenge flow from main menu --- .../Visual/Menus/TestSceneMainMenu.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 57cff38ab0d7..792b9441fcc4 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -1,11 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.Menu; using osuTK.Input; @@ -23,6 +27,47 @@ public override void SetUpSteps() AddStep("disable return to top on idle", () => Game.ChildrenOfType().Single().ReturnToTopOnIdle = false); } + [Test] + public void TestDailyChallenge() + { + AddStep("set up API", () => ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case GetRoomRequest getRoomRequest: + if (getRoomRequest.RoomId != 1234) + return false; + + var beatmap = CreateAPIBeatmap(); + beatmap.OnlineID = 1001; + getRoomRequest.TriggerSuccess(new Room + { + RoomID = { Value = 1234 }, + Playlist = + { + new PlaylistItem(beatmap) + }, + EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) } + }); + return true; + + default: + return false; + } + }); + + AddStep("beatmap of the day active", () => Game.ChildrenOfType().Single().DailyChallengeUpdated(new DailyChallengeInfo + { + RoomID = 1234, + })); + + AddStep("enter menu", () => InputManager.Key(Key.P)); + AddStep("enter submenu", () => InputManager.Key(Key.P)); + AddStep("enter daily challenge", () => InputManager.Key(Key.D)); + + AddUntilStep("wait for daily challenge screen", () => Game.ScreenStack.CurrentScreen, Is.TypeOf); + } + [Test] public void TestOnlineMenuBannerTrusted() { From 6870311c1e08f55b5a68210b58f81d8731d6b782 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 18:07:46 +0900 Subject: [PATCH 177/579] Remove requirement of specifying `animateOnnter` in `BackgroundScreen` ctor --- .../Background/TestSceneBackgroundScreenDefault.cs | 5 ----- osu.Game/Screens/BackgroundScreen.cs | 12 +++++------- osu.Game/Screens/BackgroundScreenStack.cs | 6 +++++- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 5 ----- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- .../Components/OnlinePlayBackgroundScreen.cs | 1 - .../OnlinePlay/OnlinePlayScreenWaveContainer.cs | 1 - 7 files changed, 11 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 37f2ee0b3ffa..7865d8fef7c5 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -304,11 +304,6 @@ private partial class TestBackgroundScreenDefault : BackgroundScreenDefault { private bool? lastLoadTriggerCausedChange; - public TestBackgroundScreenDefault() - : base(false) - { - } - public override bool Next() { bool didChange = base.Next(); diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index 73af9b1bf27a..53f0b39ef770 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -17,13 +17,12 @@ public abstract partial class BackgroundScreen : Screen, IEquatable base.IsPresent || Scheduler.HasPendingTasks; - protected BackgroundScreen(bool animateOnEnter = true) + public bool AnimateEntry { get; set; } = true; + + protected BackgroundScreen() { - this.animateOnEnter = animateOnEnter; Anchor = Anchor.Centre; Origin = Anchor.Centre; } @@ -53,12 +52,11 @@ protected override void Update() public override void OnEntering(ScreenTransitionEvent e) { - if (animateOnEnter) + if (AnimateEntry) { this.FadeOut(); - this.MoveToX(x_movement_amount); - this.FadeIn(TRANSITION_LENGTH, Easing.InOutQuart); + this.MoveToX(x_movement_amount); this.MoveToX(0, TRANSITION_LENGTH, Easing.InOutQuart); } diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 99ca383b9f7e..55cd27058142 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -27,10 +27,14 @@ public bool Push(BackgroundScreen? screen) if (screen == null) return false; - if (EqualityComparer.Default.Equals((BackgroundScreen)CurrentScreen, screen)) + bool isFirstScreen = CurrentScreen == null; + screen.AnimateEntry = !isFirstScreen; + + if (EqualityComparer.Default.Equals((BackgroundScreen?)CurrentScreen, screen)) return false; base.Push(screen); + return true; } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 090e00667199..7be96718bd37 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -42,11 +42,6 @@ public partial class BackgroundScreenDefault : BackgroundScreen protected virtual bool AllowStoryboardBackground => true; - public BackgroundScreenDefault(bool animateOnEnter = true) - : base(animateOnEnter) - { - } - [BackgroundDependencyLoader] private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config) { diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index ac7dffc241a3..0dc54b321f4d 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -90,7 +90,7 @@ public abstract partial class IntroScreen : StartupScreen /// protected bool UsingThemedIntro { get; private set; } - protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(false) + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault { Colour = Color4.Black }; diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs index 014473dfee40..ea422f83e39c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs @@ -21,7 +21,6 @@ public abstract partial class OnlinePlayBackgroundScreen : BackgroundScreen private PlaylistItemBackground? background; protected OnlinePlayBackgroundScreen() - : base(false) { AddInternal(new Box { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs index bfa68d82cd4f..7898e0845a08 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; From cfccd74441fcf39e4417e6eeb12dd596c6968d2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 18:07:56 +0900 Subject: [PATCH 178/579] Add daily challenge intro sequence --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- .../DailyChallenge/DailyChallengeIntro.cs | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 00b9d909a10a..dfe5460aee96 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -150,7 +150,7 @@ private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings OnPlaylists = () => this.Push(new Playlists()), OnDailyChallenge = room => { - this.Push(new DailyChallenge(room)); + this.Push(new DailyChallengeIntro(room)); }, OnExit = () => { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs new file mode 100644 index 000000000000..2ca5359f5aac --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Rooms; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengeIntro : OsuScreen + { + private readonly Room room; + + public DailyChallengeIntro(Room room) + { + this.room = room; + + ValidForResume = false; + } + + public override void OnEntering(ScreenTransitionEvent e) + { + base.OnEntering(e); + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "wangs" + } + }; + + Scheduler.AddDelayed(() => + { + this.Push(new DailyChallenge(room)); + }, 2000); + } + } +} From a0615a8f1895cfb7802cd8debe43df858f090a38 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 6 Aug 2024 11:00:04 +0300 Subject: [PATCH 179/579] Frenzi's WIP animation --- .../TestSceneDailyChallengeIntro.cs | 89 +++++++ .../DailyChallenge/DailyChallengeIntro.cs | 251 +++++++++++++++++- 2 files changed, 332 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs new file mode 100644 index 000000000000..a3541d957e4a --- /dev/null +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -0,0 +1,89 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Metadata; +using osu.Game.Tests.Visual.OnlinePlay; +using CreateRoomRequest = osu.Game.Online.Rooms.CreateRoomRequest; + +namespace osu.Game.Tests.Visual.DailyChallenge +{ + public partial class TestSceneDailyChallengeIntro : OnlinePlayTestScene + { + [Cached(typeof(MetadataClient))] + private TestMetadataClient metadataClient = new TestMetadataClient(); + + [Cached(typeof(INotificationOverlay))] + private NotificationOverlay notificationOverlay = new NotificationOverlay(); + + [BackgroundDependencyLoader] + private void load() + { + base.Content.Add(notificationOverlay); + base.Content.Add(metadataClient); + } + + [Test] + [Solo] + public void TestDailyChallenge() + { + var room = new Room + { + RoomID = { Value = 1234 }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = + { + new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallengeIntro(room))); + } + + [Test] + public void TestNotifications() + { + var room = new Room + { + RoomID = { Value = 1234 }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = + { + new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); + + Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!; + AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); + AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 2ca5359f5aac..e10b5872703e 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -1,42 +1,277 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Extensions; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.Match; +using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeIntro : OsuScreen { private readonly Room room; + private readonly PlaylistItem item; + + private FillFlowContainer introContent = null!; + private Container topPart = null!; + private Container bottomPart = null!; + private Container beatmapBackground = null!; + private Container beatmapTitle = null!; + + private bool beatmapBackgroundLoaded; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); public DailyChallengeIntro(Room room) { this.room = room; + item = room.Playlist.Single(); ValidForResume = false; } - public override void OnEntering(ScreenTransitionEvent e) - { - base.OnEntering(e); + protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); + [BackgroundDependencyLoader] + private void load() + { InternalChildren = new Drawable[] { - new OsuSpriteText + introContent = new FillFlowContainer { + Alpha = 0f, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "wangs" + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = topPart = new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = 200f }, + CornerRadius = 10f, + Masking = true, + Shear = new Vector2(OsuGame.SHEAR, 0f), + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Today's Challenge", + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + // Colour = Color4.Black, + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, + }, + beatmapBackground = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500f, 150f), + CornerRadius = 20f, + BorderColour = colourProvider.Content2, + BorderThickness = 3f, + Masking = true, + Shear = new Vector2(OsuGame.SHEAR, 0f), + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + } + }, + beatmapTitle = new Container + { + Width = 500f, + Margin = new MarginPadding { Right = 160f * OsuGame.SHEAR }, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CornerRadius = 10f, + Masking = true, + Shear = new Vector2(OsuGame.SHEAR, 0f), + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = item.Beatmap.GetDisplayString(), + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 24), + }, + } + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = bottomPart = new Container + { + Alpha = 0f, + AlwaysPresent = true, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Left = 210f }, + CornerRadius = 10f, + Masking = true, + Shear = new Vector2(OsuGame.SHEAR, 0f), + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Sunday, July 28th", + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, + } + } } }; - Scheduler.AddDelayed(() => + LoadComponentAsync(new OnlineBeatmapSetCover(item.Beatmap.BeatmapSet as IBeatmapSetOnlineInfo) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + Scale = new Vector2(1.2f), + Shear = new Vector2(-OsuGame.SHEAR, 0f), + }, c => { - this.Push(new DailyChallenge(room)); - }, 2000); + beatmapBackground.Add(c); + beatmapBackgroundLoaded = true; + updateAnimationState(); + }); + } + + private bool animationBegan; + + public override void OnEntering(ScreenTransitionEvent e) + { + base.OnEntering(e); + this.FadeInFromZero(400, Easing.OutQuint); + updateAnimationState(); + } + + public override void OnSuspending(ScreenTransitionEvent e) + { + this.FadeOut(200, Easing.OutQuint); + base.OnSuspending(e); + } + + private void updateAnimationState() + { + if (!beatmapBackgroundLoaded || !this.IsCurrentScreen()) + return; + + if (animationBegan) + return; + + beginAnimation(); + animationBegan = true; + } + + private void beginAnimation() + { + introContent.Show(); + + topPart.MoveToX(-500).MoveToX(0, 300, Easing.OutQuint) + .FadeInFromZero(400, Easing.OutQuint); + + bottomPart.MoveToX(500).MoveToX(0, 300, Easing.OutQuint) + .FadeInFromZero(400, Easing.OutQuint); + + this.Delay(400).Schedule(() => + { + introContent.AutoSizeDuration = 200; + introContent.AutoSizeEasing = Easing.OutQuint; + }); + + this.Delay(500).Schedule(() => ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item)); + + beatmapBackground.FadeOut().Delay(500) + .FadeIn(200, Easing.InQuart); + + beatmapTitle.FadeOut().Delay(500) + .FadeIn(200, Easing.InQuart); + + introContent.Delay(1800).FadeOut(200, Easing.OutQuint) + .OnComplete(_ => + { + if (this.IsCurrentScreen()) + this.Push(new DailyChallenge(room)); + }); + } + + private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen + { + private readonly OverlayColourProvider colourProvider; + + public DailyChallengeIntroBackgroundScreen(OverlayColourProvider colourProvider) + : base(null) + { + this.colourProvider = colourProvider; + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new Box + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5.Opacity(0.6f), + }); + } } } } From 083fe32d200043a4363be73948753ef3fe470030 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 18:29:15 +0900 Subject: [PATCH 180/579] Improve feel of animation --- .../DailyChallenge/DailyChallengeIntro.cs | 259 +++++++++++------- 1 file changed, 162 insertions(+), 97 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index e10b5872703e..b85cdbc2d116 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -8,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Extensions; @@ -15,8 +17,11 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Match; +using osu.Game.Screens.Play.HUD; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { @@ -25,11 +30,13 @@ public partial class DailyChallengeIntro : OsuScreen private readonly Room room; private readonly PlaylistItem item; - private FillFlowContainer introContent = null!; - private Container topPart = null!; - private Container bottomPart = null!; + private Container introContent = null!; + private Container topTitleDisplay = null!; + private Container bottomDateDisplay = null!; private Container beatmapBackground = null!; - private Container beatmapTitle = null!; + private Box flash = null!; + + private FillFlowContainer beatmapContent = null!; private bool beatmapBackgroundLoaded; @@ -49,60 +56,103 @@ public DailyChallengeIntro(Room room) [BackgroundDependencyLoader] private void load() { + Ruleset ruleset = Ruleset.Value.CreateInstance(); + InternalChildren = new Drawable[] { - introContent = new FillFlowContainer + introContent = new Container { Alpha = 0f, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 10f), + Shear = new Vector2(OsuGame.SHEAR, 0f), Children = new Drawable[] { - new Container + beatmapContent = new FillFlowContainer { + AlwaysPresent = true, // so we can get the size ahead of time + Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = topPart = new Container + Alpha = 0, + Scale = new Vector2(0.001f), + Spacing = new Vector2(10), + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = 200f }, - CornerRadius = 10f, - Masking = true, - Shear = new Vector2(OsuGame.SHEAR, 0f), - Children = new Drawable[] + beatmapBackground = new Container { - new Box + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(500f, 150f), + CornerRadius = 20f, + BorderColour = colourProvider.Content2, + BorderThickness = 3f, + Masking = true, + Children = new Drawable[] { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + flash = new Box + { + Colour = Color4.White, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Depth = float.MinValue, + } + } + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 500f, + AutoSizeAxes = Axes.Y, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Today's Challenge", - Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - // Colour = Color4.Black, - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new TruncatingSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Text = item.Beatmap.GetDisplayString(), + Padding = new MarginPadding { Vertical = 5f, Horizontal = 5f }, + Font = OsuFont.GetFont(size: 24), + }, + } + }, + new ModFlowDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Current = + { + Value = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray() }, } - }, + } }, - beatmapBackground = new Container + topTitleDisplay = new Container { Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500f, 150f), - CornerRadius = 20f, - BorderColour = colourProvider.Content2, - BorderThickness = 3f, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + CornerRadius = 10f, Masking = true, - Shear = new Vector2(OsuGame.SHEAR, 0f), Children = new Drawable[] { new Box @@ -110,18 +160,24 @@ private void load() Colour = colourProvider.Background3, RelativeSizeAxes = Axes.Both, }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Today's Challenge", + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, } }, - beatmapTitle = new Container + bottomDateDisplay = new Container { - Width = 500f, - Margin = new MarginPadding { Right = 160f * OsuGame.SHEAR }, - AutoSizeAxes = Axes.Y, Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, CornerRadius = 10f, Masking = true, - Shear = new Vector2(OsuGame.SHEAR, 0f), Children = new Drawable[] { new Box @@ -133,46 +189,13 @@ private void load() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = item.Beatmap.GetDisplayString(), + Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(), Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, Shear = new Vector2(-OsuGame.SHEAR, 0f), - Font = OsuFont.GetFont(size: 24), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), }, } }, - new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = bottomPart = new Container - { - Alpha = 0f, - AlwaysPresent = true, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Left = 210f }, - CornerRadius = 10f, - Masking = true, - Shear = new Vector2(OsuGame.SHEAR, 0f), - Children = new Drawable[] - { - new Box - { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Sunday, July 28th", - Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), - }, - } - }, - } } } }; @@ -188,12 +211,33 @@ private void load() }, c => { beatmapBackground.Add(c); + beatmapBackgroundLoaded = true; updateAnimationState(); }); } private bool animationBegan; + private bool trackContent; + + private const float initial_v_shift = 32; + private const float final_v_shift = 340; + + protected override void Update() + { + base.Update(); + + if (trackContent) + { + float vShift = initial_v_shift + (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2; + + float yPos = (float)Interpolation.DampContinuously(bottomDateDisplay.Y, vShift, 16, Clock.ElapsedFrameTime); + float xPos = (float)Interpolation.DampContinuously(bottomDateDisplay.X, getShearForY(vShift) + final_v_shift, 16, Clock.ElapsedFrameTime); + + topTitleDisplay.Position = new Vector2(-xPos, -yPos); + bottomDateDisplay.Position = new Vector2(xPos, yPos); + } + } public override void OnEntering(ScreenTransitionEvent e) { @@ -222,36 +266,57 @@ private void updateAnimationState() private void beginAnimation() { - introContent.Show(); + const float v_spacing = 0; - topPart.MoveToX(-500).MoveToX(0, 300, Easing.OutQuint) - .FadeInFromZero(400, Easing.OutQuint); + using (BeginDelayedSequence(200)) + { + introContent.Show(); - bottomPart.MoveToX(500).MoveToX(0, 300, Easing.OutQuint) - .FadeInFromZero(400, Easing.OutQuint); + topTitleDisplay.MoveToOffset(new Vector2(getShearForY(-initial_v_shift), -initial_v_shift)); + bottomDateDisplay.MoveToOffset(new Vector2(getShearForY(initial_v_shift), initial_v_shift)); - this.Delay(400).Schedule(() => - { - introContent.AutoSizeDuration = 200; - introContent.AutoSizeEasing = Easing.OutQuint; - }); + topTitleDisplay.MoveToX(getShearForY(topTitleDisplay.Y) - 500) + .MoveToX(getShearForY(topTitleDisplay.Y) - v_spacing, 300, Easing.OutQuint) + .FadeInFromZero(400, Easing.OutQuint); - this.Delay(500).Schedule(() => ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item)); + bottomDateDisplay.MoveToX(getShearForY(bottomDateDisplay.Y) + 500) + .MoveToX(getShearForY(bottomDateDisplay.Y) + v_spacing, 300, Easing.OutQuint) + .FadeInFromZero(400, Easing.OutQuint); - beatmapBackground.FadeOut().Delay(500) - .FadeIn(200, Easing.InQuart); + using (BeginDelayedSequence(500)) + { + Schedule(() => trackContent = true); + + beatmapContent + .ScaleTo(1f, 500, Easing.InQuint) + .Then() + .ScaleTo(1.1f, 3000); + + using (BeginDelayedSequence(240)) + { + beatmapContent.FadeInFromZero(280, Easing.InQuad); - beatmapTitle.FadeOut().Delay(500) - .FadeIn(200, Easing.InQuart); + flash + .Delay(400) + .FadeOutFromOne(5000, Easing.OutQuint); - introContent.Delay(1800).FadeOut(200, Easing.OutQuint) - .OnComplete(_ => + ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); + + using (BeginDelayedSequence(2600)) { - if (this.IsCurrentScreen()) - this.Push(new DailyChallenge(room)); - }); + introContent.FadeOut(200, Easing.OutQuint).OnComplete(_ => + { + if (this.IsCurrentScreen()) + this.Push(new DailyChallenge(room)); + }); + } + } + } + } } + private static float getShearForY(float yPos) => yPos * -OsuGame.SHEAR * 2; + private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen { private readonly OverlayColourProvider colourProvider; From e52d80a41b8883683021090343b4e6e8cab3852d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 18:51:44 +0900 Subject: [PATCH 181/579] Add more difficulty information and further tweaks to visuals --- .../DailyChallenge/DailyChallengeIntro.cs | 127 +++++++++++++----- 1 file changed, 97 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index b85cdbc2d116..073ed1c217cf 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,7 +13,6 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; -using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; @@ -40,6 +40,14 @@ public partial class DailyChallengeIntro : OsuScreen private bool beatmapBackgroundLoaded; + private bool animationBegan; + private bool trackContent; + + private IBindable starDifficulty = null!; + + private const float initial_v_shift = 32; + private const float final_v_shift = 340; + [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -54,10 +62,14 @@ public DailyChallengeIntro(Room room) protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); [BackgroundDependencyLoader] - private void load() + private void load(BeatmapDifficultyCache difficultyCache) { + const float horizontal_info_size = 500f; + Ruleset ruleset = Ruleset.Value.CreateInstance(); + StarRatingDisplay starRatingDisplay; + InternalChildren = new Drawable[] { introContent = new Container @@ -85,7 +97,7 @@ private void load() { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Size = new Vector2(500f, 150f), + Size = new Vector2(horizontal_info_size, 150f), CornerRadius = 20f, BorderColour = colourProvider.Content2, BorderThickness = 3f, @@ -110,7 +122,7 @@ private void load() { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Width = 500f, + Width = horizontal_info_size, AutoSizeAxes = Axes.Y, CornerRadius = 10f, Masking = true, @@ -121,28 +133,82 @@ private void load() Colour = colourProvider.Background3, RelativeSizeAxes = Axes.Both, }, - new TruncatingSpriteText + new FillFlowContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Text = item.Beatmap.GetDisplayString(), - Padding = new MarginPadding { Vertical = 5f, Horizontal = 5f }, - Font = OsuFont.GetFont(size: 24), + AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(5f), + Children = new Drawable[] + { + new TruncatingSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + MaxWidth = horizontal_info_size, + Text = item.Beatmap.BeatmapSet!.Metadata.GetDisplayTitleRomanisable(false), + Padding = new MarginPadding { Horizontal = 5f }, + Font = OsuFont.GetFont(size: 26), + }, + new TruncatingSpriteText + { + Text = $"Difficulty: {item.Beatmap.DifficultyName}", + Font = OsuFont.GetFont(size: 20, italics: true), + MaxWidth = horizontal_info_size, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new TruncatingSpriteText + { + Text = $"by {item.Beatmap.Metadata.Author.Username}", + Font = OsuFont.GetFont(size: 16, italics: true), + MaxWidth = horizontal_info_size, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + starRatingDisplay = new StarRatingDisplay(default) + { + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Margin = new MarginPadding(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } }, } }, - new ModFlowDisplay + new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Current = + Width = horizontal_info_size, + AutoSizeAxes = Axes.Y, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] { - Value = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray() - }, + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new ModFlowDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Current = + { + Value = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray() + }, + } + } } } }, @@ -200,6 +266,13 @@ private void load() } }; + starDifficulty = difficultyCache.GetBindableDifficulty(item.Beatmap); + starDifficulty.BindValueChanged(star => + { + if (star.NewValue != null) + starRatingDisplay.Current.Value = star.NewValue.Value; + }, true); + LoadComponentAsync(new OnlineBeatmapSetCover(item.Beatmap.BeatmapSet as IBeatmapSetOnlineInfo) { RelativeSizeAxes = Axes.Both, @@ -217,12 +290,6 @@ private void load() }); } - private bool animationBegan; - private bool trackContent; - - private const float initial_v_shift = 32; - private const float final_v_shift = 340; - protected override void Update() { base.Update(); @@ -248,7 +315,7 @@ public override void OnEntering(ScreenTransitionEvent e) public override void OnSuspending(ScreenTransitionEvent e) { - this.FadeOut(200, Easing.OutQuint); + this.FadeOut(800, Easing.OutQuint); base.OnSuspending(e); } @@ -290,21 +357,21 @@ private void beginAnimation() beatmapContent .ScaleTo(1f, 500, Easing.InQuint) .Then() - .ScaleTo(1.1f, 3000); + .ScaleTo(1.02f, 3000); using (BeginDelayedSequence(240)) { beatmapContent.FadeInFromZero(280, Easing.InQuad); - flash - .Delay(400) - .FadeOutFromOne(5000, Easing.OutQuint); + using (BeginDelayedSequence(300)) + Schedule(() => ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item)); - ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); + using (BeginDelayedSequence(400)) + flash.FadeOutFromOne(5000, Easing.OutQuint); using (BeginDelayedSequence(2600)) { - introContent.FadeOut(200, Easing.OutQuint).OnComplete(_ => + Schedule(() => { if (this.IsCurrentScreen()) this.Push(new DailyChallenge(room)); From 775f76f4724f5155efad42860cb1775c8dc279b0 Mon Sep 17 00:00:00 2001 From: kstefanowicz Date: Wed, 7 Aug 2024 07:47:35 -0400 Subject: [PATCH 182/579] Have placeholder text change while focused --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 10 +++++++++- .../OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 3a094cc074be..469ba19fd151 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -128,6 +128,9 @@ private void channelChanged(ValueChangedEvent e) public partial class ChatTextBox : HistoryTextBox { + public Action Focus; + public Action FocusLost; + protected override bool OnKeyDown(KeyDownEvent e) { // Chat text boxes are generally used in places where they retain focus, but shouldn't block interaction with other @@ -153,13 +156,18 @@ protected override void LoadComplete() BackgroundFocused = new Color4(10, 10, 10, 255); } + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + Focus?.Invoke(); + } + protected override void OnFocusLost(FocusLostEvent e) { base.OnFocusLost(e); FocusLost?.Invoke(); } - public Action FocusLost; } public partial class StandAloneDrawableChannel : DrawableChannel diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index 656071ad43f4..d1a73457e384 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -42,8 +42,13 @@ public GameplayChatDisplay(Room room) Background.Alpha = 0.2f; - TextBox.FocusLost = () => expandedFromTextBoxFocus.Value = false; TextBox.PlaceholderText = ChatStrings.InGameInputPlaceholder; + TextBox.Focus = () => TextBox.PlaceholderText = Resources.Localisation.Web.ChatStrings.InputPlaceholder; + TextBox.FocusLost = () => + { + TextBox.PlaceholderText = ChatStrings.InGameInputPlaceholder; + expandedFromTextBoxFocus.Value = false; + }; } protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor. From 518c1aa5a0a823a88365ca66a4cce8dae9fdbea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Aug 2024 14:01:30 +0200 Subject: [PATCH 183/579] Remove weird `Expanded` / `ExpandedState` duality --- .../TestSceneFreeModSelectOverlay.cs | 4 ++- .../TestSceneModCustomisationPanel.cs | 15 ++++++---- .../TestSceneModSelectOverlay.cs | 8 +++-- .../Overlays/Mods/ModCustomisationHeader.cs | 4 +-- .../Overlays/Mods/ModCustomisationPanel.cs | 30 ++++++++----------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++---- 6 files changed, 39 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 3097d245957d..4316653dde94 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -61,7 +61,9 @@ public void TestCustomisationNotAvailable() AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); AddWaitStep("wait some", 3); - AddAssert("customisation area not expanded", () => !this.ChildrenOfType().Single().Expanded); + AddAssert("customisation area not expanded", + () => this.ChildrenOfType().Single().ExpandedState.Value, + () => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 1ada5f40ab09..c2739e1bbdf8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -55,22 +55,26 @@ public void TestDisplay() AddStep("set DT", () => { SelectedMods.Value = new[] { new OsuModDoubleTime() }; - panel.Enabled.Value = panel.Expanded = true; + panel.Enabled.Value = true; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; }); AddStep("set DA", () => { SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }; - panel.Enabled.Value = panel.Expanded = true; + panel.Enabled.Value = true; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; }); AddStep("set FL+WU+DA+AD", () => { SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }; - panel.Enabled.Value = panel.Expanded = true; + panel.Enabled.Value = true; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; }); AddStep("set empty", () => { SelectedMods.Value = Array.Empty(); - panel.Enabled.Value = panel.Expanded = false; + panel.Enabled.Value = false; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed; }); } @@ -155,7 +159,8 @@ public void TestHoverExpandsAndCollapsesWhenHeaderClicked() private void checkExpanded(bool expanded) { - AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.Expanded, () => Is.EqualTo(expanded)); + AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.ExpandedState.Value, + () => expanded ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 00575827554d..f21c64f7fea7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -999,7 +999,9 @@ public void TestCustomisationPanelAbsorbsInput([Values] bool textSearchStartsAct AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left)); AddAssert("search still not focused", () => !this.ChildrenOfType().Single().HasFocus); AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("customisation panel closed by click", () => !this.ChildrenOfType().Single().Expanded); + AddAssert("customisation panel closed by click", + () => this.ChildrenOfType().Single().ExpandedState.Value, + () => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); if (textSearchStartsActive) AddAssert("search focused", () => this.ChildrenOfType().Single().HasFocus); @@ -1022,7 +1024,9 @@ private void changeRuleset(int id) private void assertCustomisationToggleState(bool disabled, bool active) { AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().Enabled.Value == !disabled); - AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().Expanded == active); + AddAssert($"customisation panel is {(active ? "" : "not ")}active", + () => modSelectOverlay.ChildrenOfType().Single().ExpandedState.Value, + () => active ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); } private T getSelectedMod() where T : Mod => SelectedMods.Value.OfType().Single(); diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 6d0ca7a769ed..abd48a0dcb27 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -143,8 +143,8 @@ protected override bool OnHover(HoverEvent e) { if (Enabled.Value) { - if (!touchedThisFrame) - panel.UpdateHoverExpansion(ModCustomisationPanelState.ExpandedByHover); + if (!touchedThisFrame && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed) + panel.ExpandedState.Value = ModCustomisationPanelState.ExpandedByHover; } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index a551081a7b20..f13ef2725f2f 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -38,13 +38,7 @@ public partial class ModCustomisationPanel : OverlayContainer, IKeyBindingHandle public readonly BindableBool Enabled = new BindableBool(); - public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); - - public bool Expanded - { - get => ExpandedState.Value > ModCustomisationPanelState.Collapsed; - set => ExpandedState.Value = value ? ModCustomisationPanelState.Expanded : ModCustomisationPanelState.Collapsed; - } + public readonly Bindable ExpandedState = new Bindable(); public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); @@ -52,9 +46,9 @@ public bool Expanded // Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded. // These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside - // (returning Expanded.Value to OnHover or overriding Block{Non}PositionalInput doesn't work). - public override bool HandlePositionalInput => Expanded; - public override bool HandleNonPositionalInput => Expanded; + // (handling OnHover or overriding Block{Non}PositionalInput doesn't work). + public override bool HandlePositionalInput => ExpandedState.Value != ModCustomisationPanelState.Collapsed; + public override bool HandleNonPositionalInput => ExpandedState.Value != ModCustomisationPanelState.Collapsed; [BackgroundDependencyLoader] private void load() @@ -140,7 +134,7 @@ protected override void LoadComplete() protected override bool OnClick(ClickEvent e) { - Expanded = false; + ExpandedState.Value = ModCustomisationPanelState.Collapsed; return base.OnClick(e); } @@ -153,7 +147,7 @@ public bool OnPressed(KeyBindingPressEvent e) switch (e.Action) { case GlobalAction.Back: - Expanded = false; + ExpandedState.Value = ModCustomisationPanelState.Collapsed; return true; } @@ -168,7 +162,7 @@ private void updateDisplay() { content.ClearTransforms(); - if (Expanded) + if (ExpandedState.Value != ModCustomisationPanelState.Collapsed) { content.AutoSizeDuration = 400; content.AutoSizeEasing = Easing.OutQuint; @@ -193,7 +187,7 @@ public void UpdateHoverExpansion(ModCustomisationPanelState state) private void updateMods() { - Expanded = false; + ExpandedState.Value = ModCustomisationPanelState.Collapsed; sectionsFlow.Clear(); // Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels). @@ -216,10 +210,10 @@ protected override void Update() private partial class FocusGrabbingContainer : InputBlockingContainer { - public readonly IBindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); + public readonly Bindable ExpandedState = new Bindable(); - public override bool RequestsFocus => panel.Expanded; - public override bool AcceptsFocus => panel.Expanded; + public override bool RequestsFocus => panel.ExpandedState.Value != ModCustomisationPanelState.Collapsed; + public override bool AcceptsFocus => panel.ExpandedState.Value != ModCustomisationPanelState.Collapsed; private readonly ModCustomisationPanel panel; @@ -233,7 +227,7 @@ protected override void OnHoverLost(HoverLostEvent e) if (ExpandedState.Value is ModCustomisationPanelState.ExpandedByHover && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) { - panel.UpdateHoverExpansion(ModCustomisationPanelState.Collapsed); + ExpandedState.Value = ModCustomisationPanelState.Collapsed; } base.OnHoverLost(e); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e4c52697680f..74890df5d937 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -368,18 +368,18 @@ private void updateCustomisation() customisationPanel.Enabled.Value = true; if (anyModPendingConfiguration) - customisationPanel.Expanded = true; + customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; } else { - customisationPanel.Expanded = false; + customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed; customisationPanel.Enabled.Value = false; } } private void updateCustomisationVisualState() { - if (customisationPanel.Expanded) + if (customisationPanel.ExpandedState.Value != ModCustomisationPanel.ModCustomisationPanelState.Collapsed) { columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); @@ -544,7 +544,7 @@ protected override void PopOut() nonFilteredColumnCount += 1; } - customisationPanel.Expanded = false; + customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed; } #endregion @@ -571,7 +571,7 @@ public override bool OnPressed(KeyBindingPressEvent e) // wherein activating the binding will both change the contents of the search text box and deselect all mods. case GlobalAction.DeselectAllMods: { - if (!SearchTextBox.HasFocus && !customisationPanel.Expanded) + if (!SearchTextBox.HasFocus && customisationPanel.ExpandedState.Value == ModCustomisationPanel.ModCustomisationPanelState.Collapsed) { DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick(); return true; @@ -637,7 +637,7 @@ protected override bool OnKeyDown(KeyDownEvent e) if (e.Repeat || e.Key != Key.Tab) return false; - if (customisationPanel.Expanded) + if (customisationPanel.ExpandedState.Value != ModCustomisationPanel.ModCustomisationPanelState.Collapsed) return true; // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) From f83d43c38b16512f28e479a7a163b2fdc8237427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Aug 2024 14:07:20 +0200 Subject: [PATCH 184/579] Get rid of weird method --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index f13ef2725f2f..75cd5d6c91ea 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -177,14 +177,6 @@ private void updateDisplay() } } - public void UpdateHoverExpansion(ModCustomisationPanelState state) - { - if (state > ModCustomisationPanelState.Collapsed && state <= ExpandedState.Value) - return; - - ExpandedState.Value = state; - } - private void updateMods() { ExpandedState.Value = ModCustomisationPanelState.Collapsed; From cfd7f96e76cbece16f096f0cbe518249b57ce471 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 23:29:24 +0900 Subject: [PATCH 185/579] Add missing exit line causing completely incorrect behaviour --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index c1e1142625b3..e915fdc8ec5e 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -559,6 +559,7 @@ public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) // If the import was for a different beatmap, pass the duty off to global handling. if (beatmap.BeatmapSetInfo.OnlineID != playlistItem.Beatmap.BeatmapSet!.OnlineID) { + this.Exit(); game?.PresentBeatmap(beatmap.BeatmapSetInfo, b => b.ID == beatmap.BeatmapInfo.ID); } From 10f704cc416504d24c6a6530c3edffd3534a2082 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Aug 2024 23:50:09 +0900 Subject: [PATCH 186/579] Fix xmldoc --- osu.Game/Localisation/ChatStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index 4661f9a53e81..6841e7d93893 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -25,10 +25,10 @@ public static class ChatStrings public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention"); /// - /// "press enter to type message..." + /// "press enter to chat..." /// public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to chat..."); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From 089ff559d39476a1ef4926af0a470700cce6eeff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Aug 2024 00:42:31 +0900 Subject: [PATCH 187/579] Fix inspection --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 469ba19fd151..e100b5fe5b6a 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -167,7 +167,6 @@ protected override void OnFocusLost(FocusLostEvent e) base.OnFocusLost(e); FocusLost?.Invoke(); } - } public partial class StandAloneDrawableChannel : DrawableChannel From 85805bffdefc07432624cd17a63ab28d28ee394a Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:03 -0700 Subject: [PATCH 188/579] Remove `Special#` values from `ManiaAction` and remove enum offset --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index a41e72660b4b..36ccf68d76cb 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -19,16 +19,8 @@ public ManiaInputManager(RulesetInfo ruleset, int variant) public enum ManiaAction { - [Description("Special 1")] - Special1 = 1, - - [Description("Special 2")] - Special2, - - // This offsets the start value of normal keys in-case we add more special keys - // above at a later time, without breaking replays/configs. [Description("Key 1")] - Key1 = 10, + Key1, [Description("Key 2")] Key2, From 606b0556d58e8c4b66f8469caa65d01497473584 Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:03 -0700 Subject: [PATCH 189/579] Fix key binding generators --- .../DualStageVariantGenerator.cs | 9 +++----- .../SingleStageVariantGenerator.cs | 4 +--- .../VariantMappingGenerator.cs | 21 +++++++------------ 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs index e9d26b4aa115..6a7634da01ce 100644 --- a/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs +++ b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs @@ -45,18 +45,15 @@ public IEnumerable GenerateMappings() LeftKeys = stage1LeftKeys, RightKeys = stage1RightKeys, SpecialKey = InputKey.V, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1 - }.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal); + }.GenerateKeyBindingsFor(singleStageVariant); var stage2Bindings = new VariantMappingGenerator { LeftKeys = stage2LeftKeys, RightKeys = stage2RightKeys, SpecialKey = InputKey.B, - SpecialAction = ManiaAction.Special2, - NormalActionStart = nextNormal - }.GenerateKeyBindingsFor(singleStageVariant, out _); + ActionStart = (ManiaAction)singleStageVariant, + }.GenerateKeyBindingsFor(singleStageVariant); return stage1Bindings.Concat(stage2Bindings); } diff --git a/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs index 44ffeb5ec2d3..c642da6dc479 100644 --- a/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs +++ b/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs @@ -34,8 +34,6 @@ public SingleStageVariantGenerator(int variant) LeftKeys = leftKeys, RightKeys = rightKeys, SpecialKey = InputKey.Space, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1, - }.GenerateKeyBindingsFor(variant, out _); + }.GenerateKeyBindingsFor(variant); } } diff --git a/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs b/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs index 2742ee087bbd..2195c9e1b938 100644 --- a/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs +++ b/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs @@ -26,37 +26,30 @@ public class VariantMappingGenerator public InputKey SpecialKey; /// - /// The at which the normal columns should begin. + /// The at which the columns should begin. /// - public ManiaAction NormalActionStart; - - /// - /// The for the special column. - /// - public ManiaAction SpecialAction; + public ManiaAction ActionStart; /// /// Generates a list of s for a specific number of columns. /// /// The number of columns that need to be bound. - /// The next to use for normal columns. /// The keybindings. - public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) + public IEnumerable GenerateKeyBindingsFor(int columns) { - ManiaAction currentNormalAction = NormalActionStart; + ManiaAction currentAction = ActionStart; var bindings = new List(); for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) - bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); + bindings.Add(new KeyBinding(LeftKeys[i], currentAction++)); if (columns % 2 == 1) - bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); + bindings.Add(new KeyBinding(SpecialKey, currentAction++)); for (int i = 0; i < columns / 2; i++) - bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); + bindings.Add(new KeyBinding(RightKeys[i], currentAction++)); - nextNormalAction = currentNormalAction; return bindings; } } From e7f9bba9b57b0e694abe53331e08fe82f339357e Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:04 -0700 Subject: [PATCH 190/579] Fix replay frames and auto generator --- .../Replays/ManiaAutoGenerator.cs | 23 +--- .../Replays/ManiaReplayFrame.cs | 101 +----------------- 2 files changed, 6 insertions(+), 118 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index dd3208bd89e3..a5cc94ea9afd 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -17,28 +17,9 @@ internal class ManiaAutoGenerator : AutoGenerator public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - private readonly ManiaAction[] columnActions; - public ManiaAutoGenerator(ManiaBeatmap beatmap) : base(beatmap) { - columnActions = new ManiaAction[Beatmap.TotalColumns]; - - var normalAction = ManiaAction.Key1; - var specialAction = ManiaAction.Special1; - int totalCounter = 0; - - foreach (var stage in Beatmap.Stages) - { - for (int i = 0; i < stage.Columns; i++) - { - if (stage.IsSpecialColumn(i)) - columnActions[totalCounter] = specialAction++; - else - columnActions[totalCounter] = normalAction++; - totalCounter++; - } - } } protected override void GenerateFrames() @@ -57,11 +38,11 @@ protected override void GenerateFrames() switch (point) { case HitPoint: - actions.Add(columnActions[point.Column]); + actions.Add((ManiaAction)point.Column); break; case ReleasePoint: - actions.Remove(columnActions[point.Column]); + actions.Remove((ManiaAction)point.Column); break; } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 29249ba47450..f80c4420250e 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -27,118 +25,27 @@ public ManiaReplayFrame(double time, params ManiaAction[] actions) public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { - var maniaBeatmap = (ManiaBeatmap)beatmap; - - var normalAction = ManiaAction.Key1; - var specialAction = ManiaAction.Special1; - + var action = ManiaAction.Key1; int activeColumns = (int)(legacyFrame.MouseX ?? 0); - int counter = 0; while (activeColumns > 0) { - bool isSpecial = isColumnAtIndexSpecial(maniaBeatmap, counter); - if ((activeColumns & 1) > 0) - Actions.Add(isSpecial ? specialAction : normalAction); - - if (isSpecial) - specialAction++; - else - normalAction++; + Actions.Add(action); - counter++; + action++; activeColumns >>= 1; } } public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { - var maniaBeatmap = (ManiaBeatmap)beatmap; - int keys = 0; foreach (var action in Actions) - { - switch (action) - { - case ManiaAction.Special1: - keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 0); - break; - - case ManiaAction.Special2: - keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 1); - break; - - default: - // the index in lazer, which doesn't include special keys. - int nonSpecialKeyIndex = action - ManiaAction.Key1; - - // the index inclusive of special keys. - int overallIndex = 0; - - // iterate to find the index including special keys. - for (; overallIndex < maniaBeatmap.TotalColumns; overallIndex++) - { - // skip over special columns. - if (isColumnAtIndexSpecial(maniaBeatmap, overallIndex)) - continue; - // found a non-special column to use. - if (nonSpecialKeyIndex == 0) - break; - // found a non-special column but not ours. - nonSpecialKeyIndex--; - } - - keys |= 1 << overallIndex; - break; - } - } + keys |= 1 << (int)action; return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None); } - - /// - /// Find the overall index (across all stages) for a specified special key. - /// - /// The beatmap. - /// The special key offset (0 is S1). - /// The overall index for the special column. - private int getSpecialColumnIndex(ManiaBeatmap maniaBeatmap, int specialOffset) - { - for (int i = 0; i < maniaBeatmap.TotalColumns; i++) - { - if (isColumnAtIndexSpecial(maniaBeatmap, i)) - { - if (specialOffset == 0) - return i; - - specialOffset--; - } - } - - throw new ArgumentException("Special key index is too high.", nameof(specialOffset)); - } - - /// - /// Check whether the column at an overall index (across all stages) is a special column. - /// - /// The beatmap. - /// The overall index to check. - private bool isColumnAtIndexSpecial(ManiaBeatmap beatmap, int index) - { - foreach (var stage in beatmap.Stages) - { - if (index >= stage.Columns) - { - index -= stage.Columns; - continue; - } - - return stage.IsSpecialColumn(index); - } - - throw new ArgumentException("Column index is too high.", nameof(index)); - } } } From 5ad255ecbee0d42e6860192fdcc350bf0e449e0b Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:04 -0700 Subject: [PATCH 191/579] Remove special actions from `Stage` constructor --- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 5 ++--- osu.Game.Rulesets.Mania/UI/Stage.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index b3420c49f3b8..1f388144bdf2 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -66,13 +66,12 @@ public ManiaPlayfield(List stageDefinitions) Content = new[] { new Drawable[stageDefinitions.Count] } }); - var normalColumnAction = ManiaAction.Key1; - var specialColumnAction = ManiaAction.Special1; + var columnAction = ManiaAction.Key1; int firstColumnIndex = 0; for (int i = 0; i < stageDefinitions.Count; i++) { - var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref columnAction); playfieldGrid.Content[0][i] = newStage; diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index a4a09c9a8274..86f224356138 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -59,7 +59,7 @@ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) private ISkinSource currentSkin = null!; - public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) + public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction columnStartAction) { this.firstColumnIndex = firstColumnIndex; Definition = definition; @@ -138,7 +138,7 @@ public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction n { RelativeSizeAxes = Axes.Both, Width = 1, - Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } + Action = { Value = columnStartAction++ } }; topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); From 93e193d7190c56e5995d373fe2d6a2bd290131e3 Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:04 -0700 Subject: [PATCH 192/579] Add realm migration to remap key bindings --- osu.Game/Database/RealmAccess.cs | 48 +++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ff76142bcc5c..ec86d18d4e3b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -92,8 +92,9 @@ public class RealmAccess : IDisposable /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. + /// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction /// - private const int schema_version = 41; + private const int schema_version = 42; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1145,6 +1146,51 @@ void convertOnlineIDs() where T : RealmObject } } + break; + + case 42: + for (int columns = 1; columns <= 10; columns++) + { + remapKeyBindingsForVariant(columns, false); + remapKeyBindingsForVariant(columns, true); + } + + // Replace existing key bindings with new ones reflecting changes to ManiaAction: + // - "Special#" actions are removed and "Key#" actions are inserted in their place. + // - All actions are renumbered to remove the old offsets. + void remapKeyBindingsForVariant(int columns, bool dual) + { + // https://github.com/ppy/osu/blob/8773c2f7ebc226942d6124eb95c07a83934272ea/osu.Game.Rulesets.Mania/ManiaRuleset.cs#L327-L336 + int variant = dual ? 1000 + columns * 2 : columns; + + var oldKeyBindingsQuery = migration.NewRealm + .All() + .Where(kb => kb.RulesetName == @"mania" && kb.Variant == variant); + var oldKeyBindings = oldKeyBindingsQuery.Detach(); + + migration.NewRealm.RemoveRange(oldKeyBindingsQuery); + + // https://github.com/ppy/osu/blob/8773c2f7ebc226942d6124eb95c07a83934272ea/osu.Game.Rulesets.Mania/ManiaInputManager.cs#L22-L31 + int oldNormalAction = 10; // Old Key1 offset + int oldSpecialAction = 1; // Old Special1 offset + + for (int column = 0; column < columns * (dual ? 2 : 1); column++) + { + if (columns % 2 == 1 && column % columns == columns / 2) + remapKeyBinding(oldSpecialAction++, column); + else + remapKeyBinding(oldNormalAction++, column); + } + + void remapKeyBinding(int oldAction, int newAction) + { + var oldKeyBinding = oldKeyBindings.Find(kb => kb.ActionInt == oldAction); + + if (oldKeyBinding != null) + migration.NewRealm.Add(new RealmKeyBinding(newAction, oldKeyBinding.KeyCombination, @"mania", variant)); + } + } + break; } From 48d9bc982fdf4fde520ea2cca35155023fbad34c Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:04 -0700 Subject: [PATCH 193/579] Fix tests --- .../ManiaLegacyReplayTest.cs | 12 ++++++------ .../Skinning/TestSceneStage.cs | 5 ++--- .../TestSceneAutoGeneration.cs | 8 ++++---- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 4 +--- .../Visual/Gameplay/TestScenePauseInputHandling.cs | 6 +++--- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs index 641631d05e74..de036c7b74bb 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs @@ -12,8 +12,8 @@ public class ManiaLegacyReplayTest { [TestCase(ManiaAction.Key1)] [TestCase(ManiaAction.Key1, ManiaAction.Key2)] - [TestCase(ManiaAction.Special1)] - [TestCase(ManiaAction.Key8)] + [TestCase(ManiaAction.Key5)] + [TestCase(ManiaAction.Key9)] public void TestEncodeDecodeSingleStage(params ManiaAction[] actions) { var beatmap = new ManiaBeatmap(new StageDefinition(9)); @@ -29,11 +29,11 @@ public void TestEncodeDecodeSingleStage(params ManiaAction[] actions) [TestCase(ManiaAction.Key1)] [TestCase(ManiaAction.Key1, ManiaAction.Key2)] - [TestCase(ManiaAction.Special1)] - [TestCase(ManiaAction.Special2)] - [TestCase(ManiaAction.Special1, ManiaAction.Special2)] - [TestCase(ManiaAction.Special1, ManiaAction.Key5)] + [TestCase(ManiaAction.Key3)] [TestCase(ManiaAction.Key8)] + [TestCase(ManiaAction.Key3, ManiaAction.Key8)] + [TestCase(ManiaAction.Key3, ManiaAction.Key6)] + [TestCase(ManiaAction.Key10)] public void TestEncodeDecodeDualStage(params ManiaAction[] actions) { var beatmap = new ManiaBeatmap(new StageDefinition(5)); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs index d44a38fdec2d..091a4cb55b39 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs @@ -14,12 +14,11 @@ private void load() { SetContents(_ => { - ManiaAction normalAction = ManiaAction.Key1; - ManiaAction specialAction = ManiaAction.Special1; + ManiaAction action = ManiaAction.Key1; return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) { - Child = new Stage(0, new StageDefinition(4), ref normalAction, ref specialAction) + Child = new Stage(0, new StageDefinition(4), ref action) }; }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index e3846e8213d1..9a3167b97f64 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -36,8 +36,8 @@ public void TestSingleNote() Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released"); } [Test] @@ -57,8 +57,8 @@ public void TestSingleHoldNote() Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released"); } [Test] diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index db04142915df..195365fb18d9 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -131,9 +131,7 @@ private void createBarLine(bool major) private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action) { - var specialAction = ManiaAction.Special1; - - var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction); + var stage = new Stage(0, new StageDefinition(2), ref action); stages.Add(stage); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index 2d03d0cb7ca4..bc66947ccd2b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -107,7 +107,7 @@ public void TestManiaInputNotReceivedWhilePaused() KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key4)); checkKey(() => counter, 0, false); AddStep("press space", () => InputManager.PressKey(Key.Space)); @@ -174,7 +174,7 @@ public void TestManiaPreviouslyHeldInputReleaseOnResume() KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key4)); AddStep("press space", () => InputManager.PressKey(Key.Space)); AddStep("pause", () => Player.Pause()); @@ -237,7 +237,7 @@ public void TestManiaHeldInputRemainHeldAfterResume() KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key4)); AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 1, true); From 8e63c1753677eaa8a5fa58ca90947dac32bf88ee Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 15:02:53 -0700 Subject: [PATCH 194/579] Apply CodeFactor lint --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ec86d18d4e3b..cb91d6923bb3 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1161,7 +1161,7 @@ void convertOnlineIDs() where T : RealmObject void remapKeyBindingsForVariant(int columns, bool dual) { // https://github.com/ppy/osu/blob/8773c2f7ebc226942d6124eb95c07a83934272ea/osu.Game.Rulesets.Mania/ManiaRuleset.cs#L327-L336 - int variant = dual ? 1000 + columns * 2 : columns; + int variant = dual ? 1000 + (columns * 2) : columns; var oldKeyBindingsQuery = migration.NewRealm .All() From 9bafdeeeff69a2cb122bcdd9bfaa65a71077e978 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 13:17:25 +0900 Subject: [PATCH 195/579] Improve animation --- .../DailyChallenge/DailyChallengeIntro.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 073ed1c217cf..aa18779d8116 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -45,7 +45,6 @@ public partial class DailyChallengeIntro : OsuScreen private IBindable starDifficulty = null!; - private const float initial_v_shift = 32; private const float final_v_shift = 340; [Cached] @@ -296,10 +295,10 @@ protected override void Update() if (trackContent) { - float vShift = initial_v_shift + (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2; + float vShift = (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2; float yPos = (float)Interpolation.DampContinuously(bottomDateDisplay.Y, vShift, 16, Clock.ElapsedFrameTime); - float xPos = (float)Interpolation.DampContinuously(bottomDateDisplay.X, getShearForY(vShift) + final_v_shift, 16, Clock.ElapsedFrameTime); + float xPos = (float)Interpolation.DampContinuously(bottomDateDisplay.X, getShearForY(vShift) + beatmapContent.Scale.Y * final_v_shift, 16, Clock.ElapsedFrameTime); topTitleDisplay.Position = new Vector2(-xPos, -yPos); bottomDateDisplay.Position = new Vector2(xPos, yPos); @@ -333,34 +332,32 @@ private void updateAnimationState() private void beginAnimation() { - const float v_spacing = 0; + const float v_spacing = 5; + const float initial_h_shift = 300; using (BeginDelayedSequence(200)) { introContent.Show(); - topTitleDisplay.MoveToOffset(new Vector2(getShearForY(-initial_v_shift), -initial_v_shift)); - bottomDateDisplay.MoveToOffset(new Vector2(getShearForY(initial_v_shift), initial_v_shift)); - - topTitleDisplay.MoveToX(getShearForY(topTitleDisplay.Y) - 500) + topTitleDisplay.MoveToX(getShearForY(topTitleDisplay.Y) - initial_h_shift) .MoveToX(getShearForY(topTitleDisplay.Y) - v_spacing, 300, Easing.OutQuint) .FadeInFromZero(400, Easing.OutQuint); - bottomDateDisplay.MoveToX(getShearForY(bottomDateDisplay.Y) + 500) + bottomDateDisplay.MoveToX(getShearForY(bottomDateDisplay.Y) + initial_h_shift) .MoveToX(getShearForY(bottomDateDisplay.Y) + v_spacing, 300, Easing.OutQuint) .FadeInFromZero(400, Easing.OutQuint); using (BeginDelayedSequence(500)) { - Schedule(() => trackContent = true); - beatmapContent .ScaleTo(1f, 500, Easing.InQuint) .Then() - .ScaleTo(1.02f, 3000); + .ScaleTo(1.1f, 3000); using (BeginDelayedSequence(240)) { + Schedule(() => trackContent = true); + beatmapContent.FadeInFromZero(280, Easing.InQuad); using (BeginDelayedSequence(300)) From f72f5ee7e3484b6f4644b784756962cad750f791 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 13:29:10 +0900 Subject: [PATCH 196/579] More improvements maybe --- .../DailyChallenge/DailyChallengeIntro.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index aa18779d8116..af0a015efeca 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -45,8 +45,6 @@ public partial class DailyChallengeIntro : OsuScreen private IBindable starDifficulty = null!; - private const float final_v_shift = 340; - [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -295,13 +293,11 @@ protected override void Update() if (trackContent) { - float vShift = (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2; - - float yPos = (float)Interpolation.DampContinuously(bottomDateDisplay.Y, vShift, 16, Clock.ElapsedFrameTime); - float xPos = (float)Interpolation.DampContinuously(bottomDateDisplay.X, getShearForY(vShift) + beatmapContent.Scale.Y * final_v_shift, 16, Clock.ElapsedFrameTime); + float yPos = (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2 - 20 * beatmapContent.Scale.Y; + float xPos = getShearForY(yPos) + beatmapContent.Scale.Y * 320; - topTitleDisplay.Position = new Vector2(-xPos, -yPos); - bottomDateDisplay.Position = new Vector2(xPos, yPos); + topTitleDisplay.Position = new Vector2(-xPos, yPos); + bottomDateDisplay.Position = new Vector2(xPos, -yPos); } } @@ -335,6 +331,8 @@ private void beginAnimation() const float v_spacing = 5; const float initial_h_shift = 300; + introContent.ScaleTo(1.2f, 8000); + using (BeginDelayedSequence(200)) { introContent.Show(); @@ -350,9 +348,7 @@ private void beginAnimation() using (BeginDelayedSequence(500)) { beatmapContent - .ScaleTo(1f, 500, Easing.InQuint) - .Then() - .ScaleTo(1.1f, 3000); + .ScaleTo(1f, 500, Easing.InQuint); using (BeginDelayedSequence(240)) { From 25dddb694a3db52a0bd0ed345c28728786dcc0ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 13:35:55 +0900 Subject: [PATCH 197/579] And then completely change the animation to a new style --- .../DailyChallenge/DailyChallengeIntro.cs | 170 +++++++++--------- 1 file changed, 88 insertions(+), 82 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index af0a015efeca..550b7aeda8da 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -38,10 +37,11 @@ public partial class DailyChallengeIntro : OsuScreen private FillFlowContainer beatmapContent = null!; + private Container titleContainer = null!; + private bool beatmapBackgroundLoaded; private bool animationBegan; - private bool trackContent; private IBindable starDifficulty = null!; @@ -78,6 +78,67 @@ private void load(BeatmapDifficultyCache difficultyCache) Shear = new Vector2(OsuGame.SHEAR, 0f), Children = new Drawable[] { + titleContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + topTitleDisplay = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + X = -10, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Today's Challenge", + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, + bottomDateDisplay = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + X = 10, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(), + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, + } + }, beatmapContent = new FillFlowContainer { AlwaysPresent = true, // so we can get the size ahead of time @@ -209,56 +270,6 @@ private void load(BeatmapDifficultyCache difficultyCache) } } }, - topTitleDisplay = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, - CornerRadius = 10f, - Masking = true, - Children = new Drawable[] - { - new Box - { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Today's Challenge", - Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), - }, - } - }, - bottomDateDisplay = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - CornerRadius = 10f, - Masking = true, - Children = new Drawable[] - { - new Box - { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(), - Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), - }, - } - }, } } }; @@ -287,20 +298,6 @@ private void load(BeatmapDifficultyCache difficultyCache) }); } - protected override void Update() - { - base.Update(); - - if (trackContent) - { - float yPos = (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2 - 20 * beatmapContent.Scale.Y; - float xPos = getShearForY(yPos) + beatmapContent.Scale.Y * 320; - - topTitleDisplay.Position = new Vector2(-xPos, yPos); - bottomDateDisplay.Position = new Vector2(xPos, -yPos); - } - } - public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); @@ -328,32 +325,43 @@ private void updateAnimationState() private void beginAnimation() { - const float v_spacing = 5; - const float initial_h_shift = 300; - - introContent.ScaleTo(1.2f, 8000); - using (BeginDelayedSequence(200)) { introContent.Show(); - topTitleDisplay.MoveToX(getShearForY(topTitleDisplay.Y) - initial_h_shift) - .MoveToX(getShearForY(topTitleDisplay.Y) - v_spacing, 300, Easing.OutQuint) - .FadeInFromZero(400, Easing.OutQuint); + const float y_offset_start = 260; + const float y_offset_end = 20; + + topTitleDisplay + .FadeInFromZero(400, Easing.OutQuint); + + topTitleDisplay.MoveToY(-y_offset_start) + .MoveToY(-y_offset_end, 300, Easing.OutQuint) + .Then() + .MoveToY(0, 4000); - bottomDateDisplay.MoveToX(getShearForY(bottomDateDisplay.Y) + initial_h_shift) - .MoveToX(getShearForY(bottomDateDisplay.Y) + v_spacing, 300, Easing.OutQuint) - .FadeInFromZero(400, Easing.OutQuint); + bottomDateDisplay.MoveToY(y_offset_start) + .MoveToY(y_offset_end, 300, Easing.OutQuint) + .Then() + .MoveToY(0, 4000); using (BeginDelayedSequence(500)) { beatmapContent - .ScaleTo(1f, 500, Easing.InQuint); + .ScaleTo(3) + .ScaleTo(1f, 500, Easing.In) + .Then() + .ScaleTo(1.1f, 4000); - using (BeginDelayedSequence(240)) + using (BeginDelayedSequence(100)) { - Schedule(() => trackContent = true); + titleContainer + .ScaleTo(0.4f, 400, Easing.In) + .FadeOut(500, Easing.OutQuint); + } + using (BeginDelayedSequence(240)) + { beatmapContent.FadeInFromZero(280, Easing.InQuad); using (BeginDelayedSequence(300)) @@ -375,8 +383,6 @@ private void beginAnimation() } } - private static float getShearForY(float yPos) => yPos * -OsuGame.SHEAR * 2; - private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen { private readonly OverlayColourProvider colourProvider; From 5891780427eb46527a56d4aa6acf695f9678c1f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 14:15:40 +0900 Subject: [PATCH 198/579] Show initial text a bit longer --- .../Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 550b7aeda8da..7570012e4327 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -345,7 +345,7 @@ private void beginAnimation() .Then() .MoveToY(0, 4000); - using (BeginDelayedSequence(500)) + using (BeginDelayedSequence(1000)) { beatmapContent .ScaleTo(3) From 278d887ee5ed0c34a9510afa175115ac98e4fd83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 15:10:14 +0900 Subject: [PATCH 199/579] Fix test failures due to missing room name --- osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 792b9441fcc4..613d8347b7b4 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -43,6 +43,7 @@ public void TestDailyChallenge() getRoomRequest.TriggerSuccess(new Room { RoomID = { Value = 1234 }, + Name = { Value = "Aug 8, 2024" }, Playlist = { new PlaylistItem(beatmap) From f91a3e9a350ac3f10f490059ee9ec4bfd4190d55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 14:41:22 +0900 Subject: [PATCH 200/579] Start playing daily challenge track as part of intro sequence --- .../DailyChallenge/DailyChallenge.cs | 37 ++++++++++--------- .../DailyChallenge/DailyChallengeIntro.cs | 21 ++++++++++- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index b1ff24aa48fe..6885bc050d4c 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -389,7 +389,7 @@ protected override void LoadComplete() base.LoadComplete(); beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; - beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true); + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); userModsSelectOverlay.SelectedItem.Value = playlistItem; @@ -401,15 +401,6 @@ protected override void LoadComplete() dailyChallengeInfo.BindValueChanged(dailyChallengeChanged); } - private void trySetDailyChallengeBeatmap() - { - var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID); - Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. - Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID); - - applyLoopingToTrack(); - } - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { if (state.NewValue != APIState.Online) @@ -443,7 +434,7 @@ public override void OnEntering(ScreenTransitionEvent e) waves.Show(); roomManager.JoinRoom(room); - applyLoopingToTrack(); + startLoopingTrack(this, musicController); metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t => { @@ -465,14 +456,14 @@ public override void OnEntering(ScreenTransitionEvent e) }); beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; - beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true); + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); userModsSelectOverlay.SelectedItem.Value = playlistItem; } public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); - applyLoopingToTrack(); + startLoopingTrack(this, musicController); // re-apply mods as they may have been changed by a child screen // (one known instance of this is showing a replay). updateMods(); @@ -501,17 +492,27 @@ public override bool OnExiting(ScreenExitEvent e) return base.OnExiting(e); } - private void applyLoopingToTrack() + public static void TrySetDailyChallengeBeatmap(OsuScreen screen, BeatmapManager beatmaps, RulesetStore rulesets, MusicController music, PlaylistItem item) { - if (!this.IsCurrentScreen()) + var beatmap = beatmaps.QueryBeatmap(b => b.OnlineID == item.Beatmap.OnlineID); + + screen.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. + screen.Ruleset.Value = rulesets.GetRuleset(item.RulesetID); + + startLoopingTrack(screen, music); + } + + private static void startLoopingTrack(OsuScreen screen, MusicController music) + { + if (!screen.IsCurrentScreen()) return; - var track = Beatmap.Value?.Track; + var track = screen.Beatmap.Value?.Track; if (track != null) { - Beatmap.Value?.PrepareTrackForPreview(true); - musicController.EnsurePlayingSomething(); + screen.Beatmap.Value?.PrepareTrackForPreview(true); + music.EnsurePlayingSomething(); } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 7570012e4327..83664fdd619c 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -26,6 +26,10 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeIntro : OsuScreen { + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public override bool? ApplyModTrackAdjustments => true; + private readonly Room room; private readonly PlaylistItem item; @@ -48,6 +52,15 @@ public partial class DailyChallengeIntro : OsuScreen [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + [Resolved] + private BeatmapManager beatmapManager { get; set; } = null!; + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + + [Resolved] + private MusicController musicController { get; set; } = null!; + public DailyChallengeIntro(Room room) { this.room = room; @@ -365,7 +378,13 @@ private void beginAnimation() beatmapContent.FadeInFromZero(280, Easing.InQuad); using (BeginDelayedSequence(300)) - Schedule(() => ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item)); + { + Schedule(() => + { + DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item); + ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); + }); + } using (BeginDelayedSequence(400)) flash.FadeOutFromOne(5000, Easing.OutQuint); From e95d61d4c239cfe55e48611b30cc42c0a0a6a8e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 15:01:39 +0900 Subject: [PATCH 201/579] Remove accidental double handling of beatmap availability in `DailyChallenge` --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 6885bc050d4c..9c8d0ff13374 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -455,8 +455,6 @@ public override void OnEntering(ScreenTransitionEvent e) }); }); - beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; - beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); userModsSelectOverlay.SelectedItem.Value = playlistItem; } From 5d66eda9826f7489416963e3e6fd5123f6bfd4cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 14:54:00 +0900 Subject: [PATCH 202/579] Add support for automatically downloading daily challenge during the intro display --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- .../OnlinePlay/DailyChallenge/DailyChallenge.cs | 3 +++ .../DailyChallenge/DailyChallengeIntro.cs | 13 ++++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e90b3c703f41..cd818941ffd7 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -563,7 +563,7 @@ public event Action? OnInvalidated remove => workingBeatmapCache.OnInvalidated -= value; } - public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); + public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID && !s.DeletePending)); #endregion diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 9c8d0ff13374..7cdf546080ec 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -492,6 +492,9 @@ public override bool OnExiting(ScreenExitEvent e) public static void TrySetDailyChallengeBeatmap(OsuScreen screen, BeatmapManager beatmaps, RulesetStore rulesets, MusicController music, PlaylistItem item) { + if (!screen.IsCurrentScreen()) + return; + var beatmap = beatmaps.QueryBeatmap(b => b.OnlineID == item.Beatmap.OnlineID); screen.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 83664fdd619c..7254a1f796aa 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; @@ -72,7 +73,7 @@ public DailyChallengeIntro(Room room) protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache difficultyCache) + private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config) { const float horizontal_info_size = 500f; @@ -309,11 +310,21 @@ private void load(BeatmapDifficultyCache difficultyCache) beatmapBackgroundLoaded = true; updateAnimationState(); }); + + if (config.Get(OsuSetting.AutomaticallyDownloadMissingBeatmaps)) + { + if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = item.Beatmap.BeatmapSet!.OnlineID })) + beatmapDownloader.Download(item.Beatmap.BeatmapSet!, config.Get(OsuSetting.PreferNoVideo)); + } } public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); + + beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); + this.FadeInFromZero(400, Easing.OutQuint); updateAnimationState(); } From 3f3145e109bb9ccaf4c9c3b9ec6d22952e5398ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 15:01:50 +0900 Subject: [PATCH 203/579] Start playing music during intro if download finishes early --- .../DailyChallenge/DailyChallengeIntro.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 7254a1f796aa..d00a1ef1e935 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -15,6 +15,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -53,6 +54,11 @@ public partial class DailyChallengeIntro : OsuScreen [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + [Cached] + private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + + private bool shouldBePlayingMusic; + [Resolved] private BeatmapManager beatmapManager { get; set; } = null!; @@ -83,6 +89,7 @@ private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader InternalChildren = new Drawable[] { + beatmapAvailabilityTracker, introContent = new Container { Alpha = 0f, @@ -322,8 +329,12 @@ public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); - beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; - beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); + beatmapAvailabilityTracker.SelectedItem.Value = item; + beatmapAvailabilityTracker.Availability.BindValueChanged(availability => + { + if (shouldBePlayingMusic && availability.NewValue.State == DownloadState.LocallyAvailable) + DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item); + }, true); this.FadeInFromZero(400, Easing.OutQuint); updateAnimationState(); @@ -392,6 +403,7 @@ private void beginAnimation() { Schedule(() => { + shouldBePlayingMusic = true; DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item); ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); }); From 60d383448f22f36e9cc6c95f568cd81bb0ca5bef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 16:29:54 +0900 Subject: [PATCH 204/579] Avoid making non-ruleset transformers in `Ruleset.CreateSkinTransformer` This didn't make any sense, so let's do it a better way. --- .../Argon/CatchArgonSkinTransformer.cs | 2 +- .../Legacy/CatchLegacySkinTransformer.cs | 2 +- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 2 +- .../Legacy/OsuLegacySkinTransformer.cs | 3 +- .../Argon/TaikoArgonSkinTransformer.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 14 +------ osu.Game/Skinning/ArgonSkin.cs | 21 ++++++++-- osu.Game/Skinning/ArgonSkinTransformer.cs | 40 ------------------- osu.Game/Skinning/LegacySkin.cs | 16 +++++--- osu.Game/Skinning/LegacySkinTransformer.cs | 22 +--------- osu.Game/Skinning/Skin.cs | 5 +-- osu.Game/Skinning/TrianglesSkin.cs | 2 +- .../Skinning/UserConfiguredLayoutContainer.cs | 15 +++++++ 13 files changed, 55 insertions(+), 91 deletions(-) delete mode 100644 osu.Game/Skinning/ArgonSkinTransformer.cs create mode 100644 osu.Game/Skinning/UserConfiguredLayoutContainer.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs index a67945df98eb..520c2de2480a 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs @@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Argon { - public class CatchArgonSkinTransformer : ArgonSkinTransformer + public class CatchArgonSkinTransformer : SkinTransformer { public CatchArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index abd321ddb10a..44fc3ecc0747 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -40,7 +40,7 @@ public CatchLegacySkinTransformer(ISkin skin) return base.GetDrawableComponent(lookup) as Container; // Skin has configuration. - if (base.GetDrawableComponent(lookup) is Drawable d) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; // Our own ruleset components default. diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 2cc36331ae34..ec63e1194d3b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public class OsuArgonSkinTransformer : ArgonSkinTransformer + public class OsuArgonSkinTransformer : SkinTransformer { public OsuArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 2c2f228fae4b..9a8eaa7d7d31 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -50,7 +50,7 @@ public OsuLegacySkinTransformer(ISkin skin) return base.GetDrawableComponent(lookup); // Skin has configuration. - if (base.GetDrawableComponent(lookup) is Drawable d) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; // Our own ruleset components default. @@ -74,6 +74,7 @@ public OsuLegacySkinTransformer(ISkin skin) { Children = new Drawable[] { + new LegacyComboCounter(), new LegacyKeyCounterDisplay(), } }; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs index 7d38d6c9e5d0..973b4a91ff25 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon { - public class TaikoArgonSkinTransformer : ArgonSkinTransformer + public class TaikoArgonSkinTransformer : SkinTransformer { public TaikoArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index ee010e9621d6..fb0e225c94f2 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -212,19 +212,7 @@ public virtual LegacyMods ConvertToLegacyMods(Mod[] mods) /// The source skin. /// The current beatmap. /// A skin with a transformer applied, or null if no transformation is provided by this ruleset. - public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) - { - switch (skin) - { - case LegacySkin: - return new LegacySkinTransformer(skin); - - case ArgonSkin: - return new ArgonSkinTransformer(skin); - } - - return null; - } + public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 707281db3107..85abb1edcd4c 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -7,6 +7,7 @@ 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.Game.Audio; using osu.Game.Beatmaps.Formats; @@ -93,15 +94,12 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) // Temporary until default skin has a valid hit lighting. if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty(); - if (base.GetDrawableComponent(lookup) is Drawable c) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; switch (lookup) { case SkinComponentsContainerLookup containerLookup: - // Only handle global level defaults for now. - if (containerLookup.Ruleset != null) - return null; switch (containerLookup.Target) { @@ -114,6 +112,21 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) return songSelectComponents; case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + if (containerLookup.Ruleset != null) + { + return new Container + { + RelativeSizeAxes = Axes.Both, + Child = new ArgonComboCounter + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Position = new Vector2(36, -66), + Scale = new Vector2(1.3f), + }, + }; + } + var mainHUDComponents = new DefaultSkinComponentsContainer(container => { var health = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs deleted file mode 100644 index 8ca8f79b4169..000000000000 --- a/osu.Game/Skinning/ArgonSkinTransformer.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Screens.Play.HUD; -using osuTK; - -namespace osu.Game.Skinning -{ - public class ArgonSkinTransformer : SkinTransformer - { - public ArgonSkinTransformer(ISkin skin) - : base(skin) - { - } - - public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) - { - if (lookup is SkinComponentsContainerLookup containerLookup - && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents - && containerLookup.Ruleset != null) - { - return base.GetDrawableComponent(lookup) ?? new Container - { - RelativeSizeAxes = Axes.Both, - Child = new ArgonComboCounter - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Position = new Vector2(36, -66), - Scale = new Vector2(1.3f), - }, - }; - } - - return base.GetDrawableComponent(lookup); - } - } -} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 38bf1631b462..734e80d2ed3d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -13,6 +13,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; @@ -349,19 +350,24 @@ protected override void ParseConfigurationStream(Stream stream) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (base.GetDrawableComponent(lookup) is Drawable c) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; switch (lookup) { case SkinComponentsContainerLookup containerLookup: - // Only handle global level defaults for now. - if (containerLookup.Ruleset != null) - return null; - switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + if (containerLookup.Ruleset != null) + { + return new Container + { + RelativeSizeAxes = Axes.Both, + Child = new LegacyComboCounter(), + }; + } + return new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index dbfa52de84e0..b54e9a1bdf39 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -2,42 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning { - public class LegacySkinTransformer : SkinTransformer + public abstract class LegacySkinTransformer : SkinTransformer { /// /// Whether the skin being transformed is able to provide legacy resources for the ruleset. /// public virtual bool IsProvidingLegacyResources => this.HasFont(LegacyFont.Combo); - public LegacySkinTransformer(ISkin skin) + protected LegacySkinTransformer(ISkin skin) : base(skin) { } - public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) - { - if (lookup is SkinComponentsContainerLookup containerLookup - && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents - && containerLookup.Ruleset != null) - { - return base.GetDrawableComponent(lookup) ?? new Container - { - RelativeSizeAxes = Axes.Both, - Child = new LegacyComboCounter(), - }; - } - - return base.GetDrawableComponent(lookup); - } - public override ISample? GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 5bac5c3d81cb..226d2fcb89b9 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -14,7 +14,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -26,7 +25,7 @@ namespace osu.Game.Skinning { - public abstract class Skin : IDisposable, ISkin + public abstract partial class Skin : IDisposable, ISkin { private readonly IStorageResourceProvider? resources; @@ -195,7 +194,7 @@ public void UpdateDrawableTarget(SkinComponentsContainer targetContainer) if (!LayoutInfos.TryGetValue(containerLookup.Target, out var layoutInfo)) return null; if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null; - return new Container + return new UserConfiguredLayoutContainer { RelativeSizeAxes = Axes.Both, ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance()) diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 6158d4c7bffa..29abb1949fcf 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -64,7 +64,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) // Temporary until default skin has a valid hit lighting. if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty(); - if (base.GetDrawableComponent(lookup) is Drawable c) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; switch (lookup) diff --git a/osu.Game/Skinning/UserConfiguredLayoutContainer.cs b/osu.Game/Skinning/UserConfiguredLayoutContainer.cs new file mode 100644 index 000000000000..1b5a27b53be4 --- /dev/null +++ b/osu.Game/Skinning/UserConfiguredLayoutContainer.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Skinning +{ + /// + /// This signifies that a call resolved a configuration created + /// by a user in their skin. Generally this should be given priority over any local defaults or overrides. + /// + public partial class UserConfiguredLayoutContainer : Container + { + } +} From 88c5997cb36e9642ca7519e9a5a1e1d21bd6b51e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 16:36:34 +0900 Subject: [PATCH 205/579] Add back removed xmldoc --- osu.Game/Skinning/LegacySkinTransformer.cs | 3 +++ osu.Game/Skinning/Skin.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index b54e9a1bdf39..367e5bae0178 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -8,6 +8,9 @@ namespace osu.Game.Skinning { + /// + /// Transformer used to handle support of legacy features for individual rulesets. + /// public abstract class LegacySkinTransformer : SkinTransformer { /// diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 226d2fcb89b9..fa09d0c0877a 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -25,7 +25,7 @@ namespace osu.Game.Skinning { - public abstract partial class Skin : IDisposable, ISkin + public abstract class Skin : IDisposable, ISkin { private readonly IStorageResourceProvider? resources; From 03d543ec99990aa8dbd0d56dbe8c399cda9a6065 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 16:53:22 +0900 Subject: [PATCH 206/579] Fix potential test failure in daily challenge tests See https://github.com/ppy/osu/actions/runs/10296877688/job/28500580680. --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index b1ff24aa48fe..e7dab3c4fbc7 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -455,6 +456,7 @@ public override void OnEntering(ScreenTransitionEvent e) MultiplayerPlaylistItemStats[] stats = t.GetResultSafely(); var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID); + if (itemStats == null) return; Schedule(() => @@ -462,7 +464,7 @@ public override void OnEntering(ScreenTransitionEvent e) breakdown.SetInitialCounts(itemStats.TotalScoreDistribution); totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.CumulativeScore); }); - }); + }, TaskContinuationOptions.OnlyOnRanToCompletion); beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true); From e12dba24ae2953ab70df50b55cb726eebc59a33a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 8 Aug 2024 10:49:17 +0300 Subject: [PATCH 207/579] Remove macOS/Xcode version pinning in iOS workflow --- .github/workflows/ci.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba65cfa33ac8..4abd55e3f45a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,9 +121,7 @@ jobs: build-only-ios: name: Build only (iOS) - # `macos-13` is required, because the newest Microsoft.iOS.Sdk versions require Xcode 14.3. - # TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta: https://github.com/actions/runner-images/tree/main#available-images) - runs-on: macos-13 + runs-on: macos-latest timeout-minutes: 60 steps: - name: Checkout @@ -137,8 +135,5 @@ jobs: - name: Install .NET Workloads run: dotnet workload install maui-ios - - name: Select Xcode 15.2 - run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - - name: Build run: dotnet build -c Debug osu.iOS From 64271b7bea32663b72ca5f57585dc5880cf28b03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 8 Aug 2024 13:03:00 +0300 Subject: [PATCH 208/579] Remove `iPhone`/`iPhoneSimulator` configurations --- osu.iOS.props | 6 - osu.sln | 336 ++++---------------------------------------------- 2 files changed, 24 insertions(+), 318 deletions(-) diff --git a/osu.iOS.props b/osu.iOS.props index 196d5594adb8..b77aeb6b0fd1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -16,12 +16,6 @@ false -all - - ios-arm64 - - - iossimulator-x64 - diff --git a/osu.sln b/osu.sln index aeec0843be89..829e43fc659b 100644 --- a/osu.sln +++ b/osu.sln @@ -98,445 +98,157 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|iPhone = Debug|iPhone - Debug|iPhoneSimulator = Debug|iPhoneSimulator Release|Any CPU = Release|Any CPU - Release|iPhone = Release|iPhone - Release|iPhoneSimulator = Release|iPhoneSimulator EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.Build.0 = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.ActiveCfg = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.Build.0 = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.Build.0 = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.ActiveCfg = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.Build.0 = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.Build.0 = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.ActiveCfg = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.Build.0 = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.Build.0 = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.ActiveCfg = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.Build.0 = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.Build.0 = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.ActiveCfg = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.Build.0 = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhone.Build.0 = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.ActiveCfg = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.Build.0 = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhone.ActiveCfg = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhone.Build.0 = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhone.Build.0 = Debug|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.ActiveCfg = Release|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.Build.0 = Release|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhone.ActiveCfg = Release|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhone.Build.0 = Release|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhone.Build.0 = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|Any CPU.Build.0 = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhone.ActiveCfg = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhone.Build.0 = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhone.Build.0 = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|Any CPU.Build.0 = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhone.ActiveCfg = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhone.Build.0 = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhone.Build.0 = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|Any CPU.ActiveCfg = Release|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|Any CPU.Build.0 = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhone.ActiveCfg = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhone.Build.0 = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhone.Build.0 = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.Build.0 = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhone.ActiveCfg = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhone.Build.0 = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhone.Build.0 = Debug|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.ActiveCfg = Release|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.Build.0 = Release|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhone.ActiveCfg = Release|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhone.Build.0 = Release|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhone.Build.0 = Debug|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|Any CPU.ActiveCfg = Release|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|Any CPU.Build.0 = Release|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhone.ActiveCfg = Release|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhone.Build.0 = Release|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.ActiveCfg = Debug|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.Build.0 = Debug|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.ActiveCfg = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.ActiveCfg = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.Build.0 = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.ActiveCfg = Debug|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.Build.0 = Debug|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.ActiveCfg = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.ActiveCfg = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.Build.0 = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.ActiveCfg = Debug|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.Build.0 = Debug|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.ActiveCfg = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.ActiveCfg = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.Build.0 = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.ActiveCfg = Debug|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.Build.0 = Debug|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.ActiveCfg = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.ActiveCfg = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.Build.0 = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.ActiveCfg = Debug|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.Build.0 = Debug|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.ActiveCfg = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.ActiveCfg = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.Build.0 = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.ActiveCfg = Debug|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.Build.0 = Debug|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.ActiveCfg = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.ActiveCfg = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.Build.0 = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.Build.0 = Release|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.Build.0 = Release|Any CPU + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.Build.0 = Release|Any CPU + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.Build.0 = Release|Any CPU + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.Build.0 = Release|Any CPU + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.Build.0 = Release|Any CPU + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.Build.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Build.0 = Release|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Deploy.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.ActiveCfg = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.Build.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.Deploy.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.Build.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Build.0 = Release|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Deploy.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.ActiveCfg = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.Build.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.Deploy.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Build.0 = Debug|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.Build.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.ActiveCfg = Release|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Build.0 = Release|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Deploy.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.ActiveCfg = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.Build.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.Deploy.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.Build.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Build.0 = Release|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Deploy.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.ActiveCfg = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.Build.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.Deploy.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Build.0 = Debug|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.Build.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.ActiveCfg = Release|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Build.0 = Release|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Deploy.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.ActiveCfg = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.Build.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.Deploy.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.Build.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Build.0 = Release|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Deploy.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.ActiveCfg = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.Build.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.Deploy.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|iPhone.Build.0 = Debug|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|Any CPU.ActiveCfg = Release|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|Any CPU.Build.0 = Release|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhone.ActiveCfg = Release|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhone.Build.0 = Release|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhone.Build.0 = Debug|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|Any CPU.ActiveCfg = Release|Any CPU {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|Any CPU.Build.0 = Release|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhone.ActiveCfg = Release|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhone.Build.0 = Release|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhone.Build.0 = Debug|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|Any CPU.ActiveCfg = Release|Any CPU {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|Any CPU.Build.0 = Release|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhone.ActiveCfg = Release|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhone.Build.0 = Release|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhone.Build.0 = Debug|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|Any CPU.Build.0 = Release|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhone.ActiveCfg = Release|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhone.Build.0 = Release|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhone.Build.0 = Debug|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|Any CPU.Build.0 = Release|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhone.ActiveCfg = Release|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhone.Build.0 = Release|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhone.Build.0 = Debug|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|Any CPU.Build.0 = Release|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhone.ActiveCfg = Release|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhone.Build.0 = Release|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhone.Build.0 = Debug|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|Any CPU.ActiveCfg = Release|Any CPU {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|Any CPU.Build.0 = Release|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhone.ActiveCfg = Release|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhone.Build.0 = Release|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhone.Build.0 = Debug|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|Any CPU.ActiveCfg = Release|Any CPU {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|Any CPU.Build.0 = Release|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhone.ActiveCfg = Release|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhone.Build.0 = Release|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhone.Build.0 = Debug|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|Any CPU.ActiveCfg = Release|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|Any CPU.Build.0 = Release|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhone.ActiveCfg = Release|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhone.Build.0 = Release|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d84d0310e09b00d120b263238c8c00349dc56d21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 12:56:03 +0900 Subject: [PATCH 209/579] Move mute button to master volume circle --- osu.Game/Overlays/Volume/MasterVolumeMeter.cs | 54 +++++++++++++++++++ osu.Game/Overlays/Volume/MuteButton.cs | 12 ++--- osu.Game/Overlays/Volume/VolumeMeter.cs | 16 +++--- osu.Game/Overlays/VolumeOverlay.cs | 53 ++++++++---------- 4 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 osu.Game/Overlays/Volume/MasterVolumeMeter.cs diff --git a/osu.Game/Overlays/Volume/MasterVolumeMeter.cs b/osu.Game/Overlays/Volume/MasterVolumeMeter.cs new file mode 100644 index 000000000000..951a6d53b187 --- /dev/null +++ b/osu.Game/Overlays/Volume/MasterVolumeMeter.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Volume +{ + public partial class MasterVolumeMeter : VolumeMeter + { + private MuteButton muteButton = null!; + + public Bindable IsMuted { get; } = new Bindable(); + + private readonly BindableDouble muteAdjustment = new BindableDouble(); + + [Resolved] + private VolumeOverlay volumeOverlay { get; set; } = null!; + + public MasterVolumeMeter(string name, float circleSize, Color4 meterColour) + : base(name, circleSize, meterColour) + { + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + IsMuted.BindValueChanged(muted => + { + if (muted.NewValue) + audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); + else + audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); + }); + + Add(muteButton = new MuteButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + X = CircleSize / 2, + Y = CircleSize * 0.23f, + Current = { BindTarget = IsMuted } + }); + + muteButton.Current.ValueChanged += _ => volumeOverlay.Show(); + } + + public void ToggleMute() => muteButton.Current.Value = !muteButton.Current.Value; + } +} diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 1dc8d754b720..a04d79bd2091 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -35,16 +35,16 @@ public Bindable Current private Color4 hoveredColour, unhoveredColour; - private const float width = 100; - public const float HEIGHT = 35; - public MuteButton() { + const float width = 30; + const float height = 30; + Content.BorderThickness = 3; - Content.CornerRadius = HEIGHT / 2; + Content.CornerRadius = height / 2; Content.CornerExponent = 2; - Size = new Vector2(width, HEIGHT); + Size = new Vector2(width, height); Action = () => Current.Value = !Current.Value; } @@ -52,7 +52,7 @@ public MuteButton() [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoveredColour = colours.YellowDark; + hoveredColour = colours.PinkLight; Content.BorderColour = unhoveredColour = colours.Gray1; BackgroundColour = colours.Gray1; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index e96cd0fa462e..9e0c59938606 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -35,8 +35,12 @@ public partial class VolumeMeter : Container, IStateful private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; + protected static readonly Vector2 LABEL_SIZE = new Vector2(120, 20); + public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 }; - private readonly float circleSize; + + protected readonly float CircleSize; + private readonly Color4 meterColour; private readonly string name; @@ -73,7 +77,7 @@ public SelectionState State public VolumeMeter(string name, float circleSize, Color4 meterColour) { - this.circleSize = circleSize; + CircleSize = circleSize; this.meterColour = meterColour; this.name = name; @@ -101,7 +105,7 @@ private void load(OsuColour colours, AudioManager audio) { new Container { - Size = new Vector2(circleSize), + Size = new Vector2(CircleSize), Children = new Drawable[] { new BufferedContainer @@ -199,7 +203,7 @@ private void load(OsuColour colours, AudioManager audio) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.Numeric.With(size: 0.16f * circleSize) + Font = OsuFont.Numeric.With(size: 0.16f * CircleSize) }).WithEffect(new GlowEffect { Colour = Color4.Transparent, @@ -209,10 +213,10 @@ private void load(OsuColour colours, AudioManager audio) }, new Container { - Size = new Vector2(120, 20), + Size = LABEL_SIZE, CornerRadius = 10, Masking = true, - Margin = new MarginPadding { Left = circleSize + 10 }, + Margin = new MarginPadding { Left = CircleSize + 10 }, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Children = new Drawable[] diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 6f9861c703a0..0d801ff118ed 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.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.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -20,21 +21,19 @@ namespace osu.Game.Overlays { + [Cached] public partial class VolumeOverlay : VisibilityContainer { + public Bindable IsMuted { get; } = new Bindable(); + private const float offset = 10; private VolumeMeter volumeMeterMaster = null!; private VolumeMeter volumeMeterEffect = null!; private VolumeMeter volumeMeterMusic = null!; - private MuteButton muteButton = null!; private SelectionCycleFillFlowContainer volumeMeters = null!; - private readonly BindableDouble muteAdjustment = new BindableDouble(); - - public Bindable IsMuted { get; } = new Bindable(); - [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { @@ -49,14 +48,7 @@ private void load(AudioManager audio, OsuColour colours) Width = 300, Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0)) }, - muteButton = new MuteButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding(10), - Current = { BindTarget = IsMuted } - }, - volumeMeters = new SelectionCycleFillFlowContainer + new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, @@ -64,26 +56,29 @@ private void load(AudioManager audio, OsuColour colours) Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new[] + Children = new Drawable[] { - volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), - volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), - volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), - } - } + volumeMeters = new SelectionCycleFillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Spacing = new Vector2(0, offset), + Children = new[] + { + volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), + volumeMeterMaster = new MasterVolumeMeter("MASTER", 150, colours.PinkDarker) { IsMuted = { BindTarget = IsMuted }, }, + volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), + } + }, + }, + }, }); volumeMeterMaster.Bindable.BindTo(audio.Volume); volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); - - IsMuted.BindValueChanged(muted => - { - if (muted.NewValue) - audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); - else - audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); - }); } protected override void LoadComplete() @@ -92,8 +87,6 @@ protected override void LoadComplete() foreach (var volumeMeter in volumeMeters) volumeMeter.Bindable.ValueChanged += _ => Show(); - - muteButton.Current.ValueChanged += _ => Show(); } public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false) @@ -130,7 +123,7 @@ public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false case GlobalAction.ToggleMute: Show(); - muteButton.Current.Value = !muteButton.Current.Value; + volumeMeters.OfType().First().ToggleMute(); return true; } From 0cb3b6a1f8e2668d9593eddaf9d0abe0af6936fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 22:10:26 +0900 Subject: [PATCH 210/579] Add back `TrySetDailyChallengeBeatmap` call for safety --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 9b22d368a788..5b341956bbc1 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -458,6 +458,8 @@ public override void OnEntering(ScreenTransitionEvent e) }, TaskContinuationOptions.OnlyOnRanToCompletion); userModsSelectOverlay.SelectedItem.Value = playlistItem; + + TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem); } public override void OnResuming(ScreenTransitionEvent e) From 80c814008f32b348d0cb6322ca4bb2f89607c440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 00:19:36 +0900 Subject: [PATCH 211/579] Update in line with new changes --- .../Legacy/CatchLegacySkinTransformer.cs | 3 +- .../Argon/ManiaArgonSkinTransformer.cs | 52 ++++++++-------- .../Legacy/LegacyManiaComboCounter.cs | 3 +- .../Legacy/ManiaLegacySkinTransformer.cs | 50 ++++++++-------- .../Legacy/OsuLegacySkinTransformer.cs | 59 ++++++++++--------- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 17 ++++-- 7 files changed, 97 insertions(+), 89 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 44fc3ecc0747..df8c04638d5c 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -4,7 +4,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -37,7 +36,7 @@ public CatchLegacySkinTransformer(ISkin skin) // Modifications for global components. if (containerLookup.Ruleset == null) - return base.GetDrawableComponent(lookup) as Container; + return base.GetDrawableComponent(lookup); // Skin has configuration. if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index b0a6086f2a96..f541cea0f587 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public class ManiaArgonSkinTransformer : ArgonSkinTransformer + public class ManiaArgonSkinTransformer : SkinTransformer { private readonly ManiaBeatmap beatmap; @@ -30,32 +29,31 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) switch (lookup) { case SkinComponentsContainerLookup containerLookup: - switch (containerLookup.Target) - { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME); - - var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); - - rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => - { - var combo = container.ChildrenOfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.TopCentre; - combo.Origin = Anchor.Centre; - combo.Y = 200; - } - }) - { - new ArgonManiaComboCounter(), - }; - - return rulesetHUDComponents; - } + if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) + return base.GetDrawableComponent(lookup); - break; + // Only handle per ruleset defaults here. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); + + // Skin has configuration. + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) + return d; + + return new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = 200; + } + }) + { + new ArgonManiaComboCounter(), + }; case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 00619834c8c5..a51a50c60407 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -49,7 +48,7 @@ protected override void LoadComplete() private void updateAnchor() { // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction - if (!Anchor.HasFlagFast(Anchor.y1)) + if (!Anchor.HasFlag(Anchor.y1)) { Anchor &= ~(Anchor.y0 | Anchor.y2); Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index c539c239bdbd..6ac6f6ed1898 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -82,32 +81,31 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) switch (lookup) { case SkinComponentsContainerLookup containerLookup: - switch (containerLookup.Target) - { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME); - - var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); - - rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => - { - var combo = container.ChildrenOfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.TopCentre; - combo.Origin = Anchor.Centre; - combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; - } - }) - { - new LegacyManiaComboCounter(), - }; - - return rulesetHUDComponents; - } + if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) + return base.GetDrawableComponent(lookup); + + // Modifications for global components. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); - break; + // Skin has configuration. + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) + return d; + + return new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; + } + }) + { + new LegacyManiaComboCounter(), + }; case GameplaySkinComponentLookup resultComponent: return getResult(resultComponent.Component); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 9a8eaa7d7d31..c2381fff88e6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -45,6 +45,9 @@ public OsuLegacySkinTransformer(ISkin skin) switch (lookup) { case SkinComponentsContainerLookup containerLookup: + if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) + return base.GetDrawableComponent(lookup); + // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -53,34 +56,36 @@ public OsuLegacySkinTransformer(ISkin skin) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; - // Our own ruleset components default. - switch (containerLookup.Target) + return new DefaultSkinComponentsContainer(container => { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - return new DefaultSkinComponentsContainer(container => - { - var keyCounter = container.OfType().FirstOrDefault(); - - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - }) - { - Children = new Drawable[] - { - new LegacyComboCounter(), - new LegacyKeyCounterDisplay(), - } - }; - } - - return null; + var keyCounter = container.OfType().FirstOrDefault(); + + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); + } + }) + { + Children = new Drawable[] + { + new LegacyDefaultComboCounter(), + new LegacyKeyCounterDisplay(), + } + }; case OsuSkinComponentLookup osuComponent: switch (osuComponent.Component) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index f44daa1ecbb5..7466442674cf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -443,7 +443,7 @@ public void TestMigrationLegacy() AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); - AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyComboCounter + AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyDefaultComboCounter { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 2da09839e98d..8f6e634dd6c7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -13,7 +13,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; @@ -24,6 +23,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -367,10 +367,19 @@ protected override void ParseConfigurationStream(Stream stream) case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: if (containerLookup.Ruleset != null) { - return new Container + return new DefaultSkinComponentsContainer(container => { - RelativeSizeAxes = Axes.Both, - Child = new LegacyComboCounter(), + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); + } + }) + { + new LegacyDefaultComboCounter() }; } From 7666e8b9320b8e84a4ff621cc53bf477518b7b1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 15:11:57 +0900 Subject: [PATCH 212/579] Remove `SupportsClosestAnchor` for the time being This may have had a good reason to be added, but I can't find that reason, so let's keep things simple for the time being. --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 5 +---- .../Skinning/Legacy/LegacyManiaComboCounter.cs | 4 +--- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 3 +-- osu.Game/Skinning/ISerialisableDrawable.cs | 8 -------- 5 files changed, 3 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index ad515528fb08..9a4eea993d74 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -10,20 +10,17 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; -using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public partial class ArgonManiaComboCounter : ComboCounter, ISerialisableDrawable + public partial class ArgonManiaComboCounter : ComboCounter { private OsuSpriteText text = null!; protected override double RollingDuration => 500; protected override Easing RollingEasing => Easing.OutQuint; - bool ISerialisableDrawable.SupportsClosestAnchor => false; - [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index a51a50c60407..c1fe4a10283d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -11,10 +11,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { - public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable + public partial class LegacyManiaComboCounter : LegacyComboCounter { - bool ISerialisableDrawable.SupportsClosestAnchor => false; - [BackgroundDependencyLoader] private void load(ISkinSource skin) { diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 7bbd875542f1..484af34603ff 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -444,9 +444,6 @@ private bool placeComponent(ISerialisableDrawable component, bool applyDefaults drawableComponent.Origin = Anchor.TopCentre; drawableComponent.Anchor = Anchor.TopCentre; drawableComponent.Y = targetContainer.DrawSize.Y / 2; - - if (!component.SupportsClosestAnchor) - component.UsesFixedAnchor = true; } try diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index c86221c7fb3c..722ffd6d07d6 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -103,8 +103,7 @@ protected override IEnumerable GetContextMenuItemsForSelection(IEnumer { var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors()) { - State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor), }, - Action = { Disabled = selection.Any(c => !c.Item.SupportsClosestAnchor) }, + State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } }; yield return new OsuMenuItem("Anchor") diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs index 898186bcc12c..c9dcaca6d184 100644 --- a/osu.Game/Skinning/ISerialisableDrawable.cs +++ b/osu.Game/Skinning/ISerialisableDrawable.cs @@ -27,14 +27,6 @@ public interface ISerialisableDrawable : IDrawable /// bool IsEditable => true; - /// - /// Whether this component supports the "closest" anchor. - /// - /// - /// This is disabled by some components that shift position automatically. - /// - bool SupportsClosestAnchor => true; - /// /// In the context of the skin layout editor, whether this has a permanent anchor defined. /// If , this 's is automatically determined by proximity, From 3f20f05801d8a23d970b2954cd5b956e929284c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 15:39:01 +0900 Subject: [PATCH 213/579] Remove unnecessary `UsesFixedAnchor` specifications --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 2 -- .../Skinning/Legacy/LegacyManiaComboCounter.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 9a4eea993d74..16f21098967d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -37,8 +37,6 @@ private void load(ScoreProcessor scoreProcessor) text.FadeOut(200, Easing.InQuint); } }); - - UsesFixedAnchor = true; } [Resolved] diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index c1fe4a10283d..5832210836df 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -22,8 +22,6 @@ private void load(ISkinSource skin) PopOutCountText.Anchor = Anchor.Centre; PopOutCountText.Origin = Anchor.Centre; PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; - - UsesFixedAnchor = true; } [Resolved] From 161734af954994ba2095cb77ca19498ba9fdf08f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 15:46:57 +0900 Subject: [PATCH 214/579] Simplify argon mania combo counter implementation by sharing with base counter --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 36 +------------------ .../Argon/ManiaArgonSkinTransformer.cs | 1 + .../Screens/Play/HUD/ArgonComboCounter.cs | 14 ++++---- 3 files changed, 9 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 16f21098967d..e77650bed140 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -4,41 +4,13 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public partial class ArgonManiaComboCounter : ComboCounter + public partial class ArgonManiaComboCounter : ArgonComboCounter { - private OsuSpriteText text = null!; - - protected override double RollingDuration => 500; - protected override Easing RollingEasing => Easing.OutQuint; - - [BackgroundDependencyLoader] - private void load(ScoreProcessor scoreProcessor) - { - Current.BindTo(scoreProcessor.Combo); - Current.BindValueChanged(combo => - { - if (combo.OldValue == 0 && combo.NewValue > 0) - text.FadeIn(200, Easing.OutQuint); - else if (combo.OldValue > 0 && combo.NewValue == 0) - { - if (combo.OldValue > 1) - text.FlashColour(Color4.Red, 2000, Easing.OutQuint); - - text.FadeOut(200, Easing.InQuint); - } - }); - } - [Resolved] private IScrollingInfo scrollingInfo { get; set; } = null!; @@ -47,7 +19,6 @@ private void load(ScoreProcessor scoreProcessor) protected override void LoadComplete() { base.LoadComplete(); - text.Alpha = Current.Value > 0 ? 1 : 0; direction = scrollingInfo.Direction.GetBoundCopy(); direction.BindValueChanged(_ => updateAnchor()); @@ -67,10 +38,5 @@ private void updateAnchor() if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) Y = -Y; } - - protected override IHasText CreateText() => text = new OsuSpriteText - { - Font = OsuFont.Torus.With(size: 32, fixedWidth: true), - }; } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index f541cea0f587..224db77f5901 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -46,6 +46,7 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) if (combo != null) { + combo.ShowLabel.Value = false; combo.Anchor = Anchor.TopCentre; combo.Origin = Anchor.Centre; combo.Y = 200; diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index db0480c5663f..3f74a8d4e8b5 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonComboCounter : ComboCounter { - private ArgonCounterTextComponent text = null!; + protected ArgonCounterTextComponent Text = null!; protected override double RollingDuration => 250; @@ -43,16 +43,16 @@ private void load(ScoreProcessor scoreProcessor) bool wasIncrease = combo.NewValue > combo.OldValue; bool wasMiss = combo.OldValue > 1 && combo.NewValue == 0; - float newScale = Math.Clamp(text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f); + float newScale = Math.Clamp(Text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f); float duration = wasMiss ? 2000 : 500; - text.NumberContainer + Text.NumberContainer .ScaleTo(new Vector2(newScale)) .ScaleTo(Vector2.One, duration, Easing.OutQuint); if (wasMiss) - text.FlashColour(Color4.Red, duration, Easing.OutQuint); + Text.FlashColour(Color4.Red, duration, Easing.OutQuint); }); } @@ -70,8 +70,8 @@ private void updateWireframe() { int digitsRequiredForDisplayCount = getDigitsRequiredForDisplayCount(); - if (digitsRequiredForDisplayCount != text.WireframeTemplate.Length) - text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount); + if (digitsRequiredForDisplayCount != Text.WireframeTemplate.Length) + Text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount); } private int getDigitsRequiredForDisplayCount() @@ -86,7 +86,7 @@ private int getDigitsRequiredForDisplayCount() protected override LocalisableString FormatCount(int count) => $@"{count}x"; - protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) + protected override IHasText CreateText() => Text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) { WireframeOpacity = { BindTarget = WireframeOpacity }, ShowLabel = { BindTarget = ShowLabel }, From 2114f092c7422d11d16020c26865f29fd3f2d4c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 16:31:47 +0900 Subject: [PATCH 215/579] Add failing test coverage showing coordinate truncation --- .../Formats/LegacyBeatmapDecoderTest.cs | 34 +++++++++++++++++++ .../Resources/hitobject-coordinates-lazer.osu | 6 ++++ .../hitobject-coordinates-legacy.osu | 5 +++ 3 files changed, 45 insertions(+) create mode 100644 osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu create mode 100644 osu.Game.Tests/Resources/hitobject-coordinates-legacy.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 19378821b302..54ebebeb7bbd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -468,6 +468,40 @@ public void TestDecodeBeatmapComboOffsetsCatch() } } + [Test] + public void TestDecodeBeatmapHitObjectCoordinatesLegacy() + { + var decoder = new LegacyBeatmapDecoder(); + + using (var resStream = TestResources.OpenResource("hitobject-coordinates-legacy.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + var positionData = hitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(256, 256), positionData!.Position); + } + } + + [Test] + public void TestDecodeBeatmapHitObjectCoordinatesLazer() + { + var decoder = new LegacyBeatmapDecoder(LegacyBeatmapEncoder.FIRST_LAZER_VERSION); + + using (var resStream = TestResources.OpenResource("hitobject-coordinates-lazer.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + var positionData = hitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(256.99853f, 256.001f), positionData!.Position); + } + } + [Test] public void TestDecodeBeatmapHitObjects() { diff --git a/osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu b/osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu new file mode 100644 index 000000000000..bb898a152163 --- /dev/null +++ b/osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu @@ -0,0 +1,6 @@ +osu file format v128 + +[HitObjects] +// Coordinates should be preserves in lazer beatmaps. + +256.99853,256.001,1000,49,0,0:0:0:0: diff --git a/osu.Game.Tests/Resources/hitobject-coordinates-legacy.osu b/osu.Game.Tests/Resources/hitobject-coordinates-legacy.osu new file mode 100644 index 000000000000..e914c2fb36ea --- /dev/null +++ b/osu.Game.Tests/Resources/hitobject-coordinates-legacy.osu @@ -0,0 +1,5 @@ +osu file format v14 + +[HitObjects] +// Coordinates should be truncated to int values in legacy beatmaps. +256.99853,256.001,1000,49,0,0:0:0:0: From d072c6a743ffb6a2a3614587703de90588dbe169 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 16:19:25 +0900 Subject: [PATCH 216/579] Fix hit object coordinates being truncated to `int` values Closes https://github.com/ppy/osu/issues/29340. --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 6 ++++++ osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 30a78a16ed94..ca4fadf45886 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -18,6 +18,12 @@ public abstract class LegacyDecoder : Decoder { public const int LATEST_VERSION = 14; + /// + /// The .osu format (beatmap) version. + /// + /// osu!stable's versions end at . + /// osu!lazer's versions starts at . + /// protected readonly int FormatVersion; protected LegacyDecoder(int version) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 37a87462ca51..8e6ffa20cc1b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -32,7 +32,7 @@ public abstract class ConvertHitObjectParser : HitObjectParser protected readonly double Offset; /// - /// The beatmap version. + /// The .osu format (beatmap) version. /// protected readonly int FormatVersion; @@ -48,7 +48,10 @@ public override HitObject Parse(string text) { string[] split = text.Split(','); - Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); + Vector2 pos = + FormatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION + ? new Vector2(Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)) + : new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); double startTime = Parsing.ParseDouble(split[2]) + Offset; From 52b2d73e046dd4958658b75212d161dbbe176077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:10:18 +0900 Subject: [PATCH 217/579] Only show daily challenge notification if it started within the last 30 minutes --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index a5616b95a0f3..b1f276b2ecc1 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -104,8 +104,7 @@ protected override void LoadComplete() { base.LoadComplete(); - info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true)); - dailyChallengeChanged(postNotification: false); + info.BindValueChanged(dailyChallengeChanged, true); } protected override void Update() @@ -131,28 +130,29 @@ protected override void Update() } } - private void dailyChallengeChanged(bool postNotification) + private void dailyChallengeChanged(ValueChangedEvent info) { UpdateState(); scheduledCountdownUpdate?.Cancel(); scheduledCountdownUpdate = null; - if (info.Value == null) + if (this.info.Value == null) { Room = null; cover.OnlineInfo = TooltipContent = null; } else { - var roomRequest = new GetRoomRequest(info.Value.Value.RoomID); + var roomRequest = new GetRoomRequest(this.info.Value.Value.RoomID); roomRequest.Success += room => { Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; - if (postNotification) + // We only want to notify the user if a new challenge recently went live. + if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600) notificationOverlay?.Post(new NewDailyChallengeNotification(room)); updateCountdown(); From e146c8e23083307c5ea2f222243b16bf612bbcc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:11:11 +0900 Subject: [PATCH 218/579] Ensure only one daily challenge notification is fired per room --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index b1f276b2ecc1..ac04afdc4d7a 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -130,6 +130,8 @@ protected override void Update() } } + private long? lastNotifiedDailyChallengeRoomId; + private void dailyChallengeChanged(ValueChangedEvent info) { UpdateState(); @@ -152,8 +154,11 @@ private void dailyChallengeChanged(ValueChangedEvent info) cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; // We only want to notify the user if a new challenge recently went live. - if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600) + if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600 && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) + { + lastNotifiedDailyChallengeRoomId = room.RoomID.Value; notificationOverlay?.Post(new NewDailyChallengeNotification(room)); + } updateCountdown(); Scheduler.AddDelayed(updateCountdown, 1000, true); From f6ada68e47b8f686b8d7a78a43652c67cda48ca0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:26:54 +0900 Subject: [PATCH 219/579] Fix migration failure due to change in class name --- osu.Game/Skinning/Skin.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index a885a0083d54..3a83815f0eaf 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -251,13 +251,15 @@ private void applyMigration(SkinLayoutInfo layout, SkinComponentsContainerLookup { case 1: { + // Combo counters were moved out of the global HUD components into per-ruleset. + // This is to allow some rulesets to customise further (ie. mania and catch moving the combo to within their play area). if (target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents || !layout.TryGetDrawableInfo(null, out var globalHUDComponents) || resources == null) break; var comboCounters = globalHUDComponents.Where(c => - c.Type.Name == nameof(LegacyComboCounter) || + c.Type.Name == nameof(LegacyDefaultComboCounter) || c.Type.Name == nameof(DefaultComboCounter) || c.Type.Name == nameof(ArgonComboCounter)).ToArray(); From 0a8e342830059a0c71a5b0515038d70146319e28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:37:39 +0900 Subject: [PATCH 220/579] Fix occasionally `ChatOverlay` test failures due to RNG usage See https://github.com/ppy/osu/actions/runs/10302758137/job/28517150950. Same ID gets chosen twice for PM channel. --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 58feab4ebbef..7f9b9acf1c46 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -717,9 +717,12 @@ private List createChannelMessages(Channel channel) Type = ChannelType.Public, }; + private static int privateChannelUser = DummyAPIAccess.DUMMY_USER_ID + 1; + private Channel createPrivateChannel() { - int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1); + int id = Interlocked.Increment(ref privateChannelUser); + return new Channel(new APIUser { Id = id, From 18c80870d81e004a0b8979015bcb15a135db00a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:40:48 +0900 Subject: [PATCH 221/579] Update one more RNG usage in same tests --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 7f9b9acf1c46..372cf608530d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -19,7 +19,6 @@ using osu.Framework.Input; using osu.Framework.Logging; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -122,7 +121,7 @@ public void SetUpSteps() return true; case PostMessageRequest postMessage: - postMessage.TriggerSuccess(new Message(RNG.Next(0, 10000000)) + postMessage.TriggerSuccess(new Message(getNextTestID()) { Content = postMessage.Message.Content, ChannelId = postMessage.Message.ChannelId, @@ -717,11 +716,9 @@ private List createChannelMessages(Channel channel) Type = ChannelType.Public, }; - private static int privateChannelUser = DummyAPIAccess.DUMMY_USER_ID + 1; - private Channel createPrivateChannel() { - int id = Interlocked.Increment(ref privateChannelUser); + int id = getNextTestID(); return new Channel(new APIUser { @@ -742,6 +739,10 @@ private Channel createAnnounceChannel() }; } + private static int testId = DummyAPIAccess.DUMMY_USER_ID + 1; + + private static int getNextTestID() => Interlocked.Increment(ref testId); + private partial class TestChatOverlay : ChatOverlay { public bool SlowLoading { get; set; } From c8a77271994778a53eed43a37cbe07a324dc07f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:46:10 +0900 Subject: [PATCH 222/579] Make ID retrieval global to all tests and fix multiple other usages --- osu.Game.Tests/Resources/TestResources.cs | 9 +++++++-- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 4 ++-- osu.Game.Tests/Visual/Online/TestSceneChannelList.cs | 8 ++++---- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 9 +++------ 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index a77dc8d49b90..e0572e604c1d 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -73,7 +73,12 @@ public static string GetTestBeatmapForImport(bool virtualTrack = false) private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz"); - private static int importId; + private static int testId = 1; + + /// + /// Get a unique int value which is incremented each call. + /// + public static int GetNextTestID() => Interlocked.Increment(ref testId); /// /// Create a test beatmap set model. @@ -88,7 +93,7 @@ public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = nul RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length]; - int setId = Interlocked.Increment(ref importId); + int setId = GetNextTestID(); var metadata = new BeatmapMetadata { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3306b6624e05..ad7e21135456 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -139,8 +139,8 @@ public void TestLobbyEvents() private void addRandomPlayer() { - int randomUser = RNG.Next(200000, 500000); - multiplayerClient.AddUser(new APIUser { Id = randomUser, Username = $"user {randomUser}" }); + int id = TestResources.GetNextTestID(); + multiplayerClient.AddUser(new APIUser { Id = id, Username = $"user {id}" }); } private void removeLastUser() diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index a0cca5f53d5d..5f77e084da35 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -9,13 +9,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Chat.ChannelList; using osu.Game.Overlays.Chat.Listing; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Online { @@ -160,7 +160,7 @@ public void TestVisual() private Channel createRandomPublicChannel() { - int id = RNG.Next(0, 10000); + int id = TestResources.GetNextTestID(); return new Channel { Name = $"#channel-{id}", @@ -171,7 +171,7 @@ private Channel createRandomPublicChannel() private Channel createRandomPrivateChannel() { - int id = RNG.Next(0, 10000); + int id = TestResources.GetNextTestID(); return new Channel(new APIUser { Id = id, @@ -181,7 +181,7 @@ private Channel createRandomPrivateChannel() private Channel createRandomAnnounceChannel() { - int id = RNG.Next(0, 10000); + int id = TestResources.GetNextTestID(); return new Channel { Name = $"Announce {id}", diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 372cf608530d..a47205094e7e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -32,6 +32,7 @@ using osuTK; using osuTK.Input; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Online { @@ -121,7 +122,7 @@ public void SetUpSteps() return true; case PostMessageRequest postMessage: - postMessage.TriggerSuccess(new Message(getNextTestID()) + postMessage.TriggerSuccess(new Message(TestResources.GetNextTestID()) { Content = postMessage.Message.Content, ChannelId = postMessage.Message.ChannelId, @@ -718,7 +719,7 @@ private List createChannelMessages(Channel channel) private Channel createPrivateChannel() { - int id = getNextTestID(); + int id = TestResources.GetNextTestID(); return new Channel(new APIUser { @@ -739,10 +740,6 @@ private Channel createAnnounceChannel() }; } - private static int testId = DummyAPIAccess.DUMMY_USER_ID + 1; - - private static int getNextTestID() => Interlocked.Increment(ref testId); - private partial class TestChatOverlay : ChatOverlay { public bool SlowLoading { get; set; } From 8fdd94090b6fde9550ae8c1bac4e51044dc971da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 18:02:37 +0900 Subject: [PATCH 223/579] Show object inspector values during placement --- .../Edit/OsuHitObjectInspector.cs | 10 +++---- .../Compose/Components/HitObjectInspector.cs | 27 +++++++++++++------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs index 27e7d5497cab..b31fe0599573 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs @@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuHitObjectInspector : HitObjectInspector { - protected override void AddInspectorValues() + protected override void AddInspectorValues(HitObject[] objects) { - base.AddInspectorValues(); + base.AddInspectorValues(objects); - if (EditorBeatmap.SelectedHitObjects.Count > 0) + if (objects.Length > 0) { - var firstInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MinBy(ho => ho.StartTime)!; - var lastInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MaxBy(ho => ho.GetEndTime())!; + var firstInSelection = (OsuHitObject)objects.MinBy(ho => ho.StartTime)!; + var lastInSelection = (OsuHitObject)objects.MaxBy(ho => ho.GetEndTime())!; Debug.Assert(firstInSelection != null && lastInSelection != null); diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs index de23147e7bbc..b74a89e3fefc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Threading; @@ -16,6 +17,7 @@ protected override void LoadComplete() base.LoadComplete(); EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText(); + EditorBeatmap.PlacementObject.BindValueChanged(_ => updateInspectorText()); EditorBeatmap.TransactionBegan += updateInspectorText; EditorBeatmap.TransactionEnded += updateInspectorText; updateInspectorText(); @@ -29,24 +31,33 @@ private void updateInspectorText() rollingTextUpdate?.Cancel(); rollingTextUpdate = null; - AddInspectorValues(); + HitObject[] objects; + + if (EditorBeatmap.SelectedHitObjects.Count > 0) + objects = EditorBeatmap.SelectedHitObjects.ToArray(); + else if (EditorBeatmap.PlacementObject.Value != null) + objects = new[] { EditorBeatmap.PlacementObject.Value }; + else + objects = Array.Empty(); + + AddInspectorValues(objects); // I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes. // This is a good middle-ground for the time being. - if (EditorBeatmap.SelectedHitObjects.Count > 0) + if (objects.Length > 0) rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250); } - protected virtual void AddInspectorValues() + protected virtual void AddInspectorValues(HitObject[] objects) { - switch (EditorBeatmap.SelectedHitObjects.Count) + switch (objects.Length) { case 0: AddValue("No selection"); break; case 1: - var selected = EditorBeatmap.SelectedHitObjects.Single(); + var selected = objects.Single(); AddHeader("Type"); AddValue($"{selected.GetType().ReadableName()}"); @@ -105,13 +116,13 @@ protected virtual void AddInspectorValues() default: AddHeader("Selected Objects"); - AddValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}"); + AddValue($"{objects.Length:#,0.##}"); AddHeader("Start Time"); - AddValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms"); + AddValue($"{objects.Min(o => o.StartTime):#,0.##}ms"); AddHeader("End Time"); - AddValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms"); + AddValue($"{objects.Max(o => o.GetEndTime()):#,0.##}ms"); break; } } From 3e634a14a4794e8b18f5920c807180896e31603c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 18:43:37 +0900 Subject: [PATCH 224/579] Add temporary debug code for multiplayer test failures --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 77ede1fd35d8..ee2f1d64dcf5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -782,7 +782,21 @@ public Task PlaylistItemChanged(MultiplayerPlaylistItem item) } catch (Exception ex) { - throw new AggregateException($"Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex); + // Temporary code to attempt to figure out long-term failing tests. + bool success = true; + int indexOf = -1234; + + try + { + indexOf = Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID)); + Room.Playlist[indexOf] = item; + } + catch + { + success = false; + } + + throw new AggregateException($"Index: {indexOf} Length: {Room.Playlist.Count} Retry success: {success} Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex); } ItemChanged?.Invoke(item); From fa9a835eb5366ea55def9aa280700c55b51ef28f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 19:29:23 +0900 Subject: [PATCH 225/579] Make icon smaller --- osu.Game/Overlays/Volume/MuteButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index a04d79bd2091..bee5cce19980 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -71,7 +71,7 @@ private void load(OsuColour colours) Current.BindValueChanged(muted => { icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp; - icon.Size = new Vector2(muted.NewValue ? 18 : 20); + icon.Size = new Vector2(muted.NewValue ? 12 : 16); icon.Margin = new MarginPadding { Right = muted.NewValue ? 2 : 0 }; }, true); } From 179a3ad8dd31c8ecfb4684a2ad5529b91a87d1ae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Aug 2024 19:55:56 +0900 Subject: [PATCH 226/579] Hack around the border looking ugly This is an o!f issue because borders are applied into the individual sprites of the container via masking, rather than being isolated to the container itself. In this case, it'll be applied to the "flash" sprite, which is using additive blending, causing further issues. --- osu.Game/Overlays/Volume/MuteButton.cs | 32 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index bee5cce19980..878842867d27 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -7,13 +7,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Volume { @@ -33,29 +33,28 @@ public Bindable Current } } - private Color4 hoveredColour, unhoveredColour; + private ColourInfo hoveredBorderColour; + private ColourInfo unhoveredBorderColour; + private CompositeDrawable border = null!; public MuteButton() { const float width = 30; const float height = 30; - Content.BorderThickness = 3; + Size = new Vector2(width, height); Content.CornerRadius = height / 2; Content.CornerExponent = 2; - Size = new Vector2(width, height); - Action = () => Current.Value = !Current.Value; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoveredColour = colours.PinkLight; - - Content.BorderColour = unhoveredColour = colours.Gray1; BackgroundColour = colours.Gray1; + hoveredBorderColour = colours.PinkLight; + unhoveredBorderColour = colours.Gray1; SpriteIcon icon; @@ -65,6 +64,19 @@ private void load(OsuColour colours) { Anchor = Anchor.Centre, Origin = Anchor.Centre, + }, + border = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 3, + BorderColour = unhoveredBorderColour, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } } }); @@ -78,13 +90,13 @@ private void load(OsuColour colours) protected override bool OnHover(HoverEvent e) { - Content.TransformTo, ColourInfo>("BorderColour", hoveredColour, 500, Easing.OutQuint); + border.TransformTo(nameof(BorderColour), hoveredBorderColour, 500, Easing.OutQuint); return false; } protected override void OnHoverLost(HoverLostEvent e) { - Content.TransformTo, ColourInfo>("BorderColour", unhoveredColour, 500, Easing.OutQuint); + border.TransformTo(nameof(BorderColour), unhoveredBorderColour, 500, Easing.OutQuint); } protected override bool OnMouseDown(MouseDownEvent e) From 3896a081a56529044848830bd913bdc3313d16df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 22:50:56 +0900 Subject: [PATCH 227/579] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3b3385ecfe36..ae30563a19c8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 196d5594adb8..9fa1b691e90f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From c6fa348d82f48f07d516885976288c4aa437f3bc Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 9 Aug 2024 23:03:03 +0900 Subject: [PATCH 228/579] Add sound design for daily challenge intro animation --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- .../DailyChallenge/DailyChallengeIntro.cs | 86 ++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index e9fff9bb07f2..0997ab80039f 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -138,7 +138,7 @@ private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host) }); buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, Key.M)); buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, Key.L)); - buttonsPlay.Add(new DailyChallengeButton(@"button-default-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D)); + buttonsPlay.Add(new DailyChallengeButton(@"button-daily-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index d00a1ef1e935..83ea0f8088af 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -4,6 +4,8 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -68,6 +70,18 @@ public partial class DailyChallengeIntro : OsuScreen [Resolved] private MusicController musicController { get; set; } = null!; + private Sample? dateWindupSample; + private Sample? dateImpactSample; + private Sample? beatmapWindupSample; + private Sample? beatmapImpactSample; + + private SampleChannel? dateWindupChannel; + private SampleChannel? dateImpactChannel; + private SampleChannel? beatmapWindupChannel; + private SampleChannel? beatmapImpactChannel; + + private IDisposable? duckOperation; + public DailyChallengeIntro(Room room) { this.room = room; @@ -79,7 +93,7 @@ public DailyChallengeIntro(Room room) protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config) + private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config, AudioManager audio) { const float horizontal_info_size = 500f; @@ -323,6 +337,11 @@ private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = item.Beatmap.BeatmapSet!.OnlineID })) beatmapDownloader.Download(item.Beatmap.BeatmapSet!, config.Get(OsuSetting.PreferNoVideo)); } + + dateWindupSample = audio.Samples.Get(@"DailyChallenge/date-windup"); + dateImpactSample = audio.Samples.Get(@"DailyChallenge/date-impact"); + beatmapWindupSample = audio.Samples.Get(@"DailyChallenge/beatmap-windup"); + beatmapImpactSample = audio.Samples.Get(@"DailyChallenge/beatmap-impact"); } public override void OnEntering(ScreenTransitionEvent e) @@ -338,6 +357,8 @@ public override void OnEntering(ScreenTransitionEvent e) this.FadeInFromZero(400, Easing.OutQuint); updateAnimationState(); + + playDateWindupSample(); } public override void OnSuspending(ScreenTransitionEvent e) @@ -346,6 +367,12 @@ public override void OnSuspending(ScreenTransitionEvent e) base.OnSuspending(e); } + protected override void Dispose(bool isDisposing) + { + resetAudio(); + base.Dispose(isDisposing); + } + private void updateAnimationState() { if (!beatmapBackgroundLoaded || !this.IsCurrentScreen()) @@ -380,6 +407,29 @@ private void beginAnimation() .Then() .MoveToY(0, 4000); + using (BeginDelayedSequence(150)) + { + Schedule(() => + { + playDateImpactSample(); + playBeatmapWindupSample(); + + duckOperation?.Dispose(); + duckOperation = musicController.Duck(new DuckParameters + { + RestoreDuration = 1500f, + }); + }); + + using (BeginDelayedSequence(2750)) + { + Schedule(() => + { + duckOperation?.Dispose(); + }); + } + } + using (BeginDelayedSequence(1000)) { beatmapContent @@ -406,6 +456,7 @@ private void beginAnimation() shouldBePlayingMusic = true; DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item); ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); + playBeatmapImpactSample(); }); } @@ -425,6 +476,39 @@ private void beginAnimation() } } + private void playDateWindupSample() + { + dateWindupChannel = dateWindupSample?.GetChannel(); + dateWindupChannel?.Play(); + } + + private void playDateImpactSample() + { + dateImpactChannel = dateImpactSample?.GetChannel(); + dateImpactChannel?.Play(); + } + + private void playBeatmapWindupSample() + { + beatmapWindupChannel = beatmapWindupSample?.GetChannel(); + beatmapWindupChannel?.Play(); + } + + private void playBeatmapImpactSample() + { + beatmapImpactChannel = beatmapImpactSample?.GetChannel(); + beatmapImpactChannel?.Play(); + } + + private void resetAudio() + { + dateWindupChannel?.Stop(); + dateImpactChannel?.Stop(); + beatmapWindupChannel?.Stop(); + beatmapImpactChannel?.Stop(); + duckOperation?.Dispose(); + } + private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen { private readonly OverlayColourProvider colourProvider; From 81777f22b4463bafa8de3da717db42193b7f904a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Aug 2024 00:11:39 +0900 Subject: [PATCH 229/579] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index ae30563a19c8..b5a355a77f01 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fa1b691e90f..7b3903c352a4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From c29b40ae6593e21d3fdebcb4a8dbff37f1edbbca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Aug 2024 02:08:02 +0900 Subject: [PATCH 230/579] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c4d76a2441c5..6b07a33af2a9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 2dee8bef7e861b435bcaf8c737c8255b5aafe0a4 Mon Sep 17 00:00:00 2001 From: ArijanJ Date: Fri, 9 Aug 2024 22:50:37 +0200 Subject: [PATCH 231/579] Add option to hide song progress time/text --- osu.Game/Localisation/HUD/SongProgressStrings.cs | 10 ++++++++++ osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 4 ++++ osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 13 +++++++++++++ 3 files changed, 27 insertions(+) diff --git a/osu.Game/Localisation/HUD/SongProgressStrings.cs b/osu.Game/Localisation/HUD/SongProgressStrings.cs index 4c621e8e8c72..332f15cb174e 100644 --- a/osu.Game/Localisation/HUD/SongProgressStrings.cs +++ b/osu.Game/Localisation/HUD/SongProgressStrings.cs @@ -19,6 +19,16 @@ public static class SongProgressStrings /// public static LocalisableString ShowGraphDescription => new TranslatableString(getKey(@"show_graph_description"), "Whether a graph displaying difficulty throughout the beatmap should be shown"); + /// + /// "Show time" + /// + public static LocalisableString ShowTime => new TranslatableString(getKey(@"show_time"), "Show time"); + + /// + /// "Whether the passed and remaining time should be shown" + /// + public static LocalisableString ShowTimeDescription => new TranslatableString(getKey(@"show_time_description"), "Whether the passed and remaining time should be shown"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 7db3f9fd3ceb..ebebfebfb3f9 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -26,6 +26,9 @@ public partial class ArgonSongProgress : SongProgress [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))] public Bindable ShowGraph { get; } = new BindableBool(true); + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] + public Bindable ShowTime { get; } = new BindableBool(true); + [Resolved] private Player? player { get; set; } @@ -90,6 +93,7 @@ protected override void LoadComplete() Interactive.BindValueChanged(_ => bar.Interactive = Interactive.Value, true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); + ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true); } protected override void UpdateObjects(IEnumerable objects) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index f01c11855cb7..1586ac72a1e5 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -33,6 +33,9 @@ public partial class DefaultSongProgress : SongProgress [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))] public Bindable ShowGraph { get; } = new BindableBool(true); + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] + public Bindable ShowTime { get; } = new BindableBool(true); + [Resolved] private Player? player { get; set; } @@ -82,9 +85,11 @@ protected override void LoadComplete() { Interactive.BindValueChanged(_ => updateBarVisibility(), true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); + ShowTime.BindValueChanged(_ => updateTimeVisibility(), true); base.LoadComplete(); } + protected override void UpdateObjects(IEnumerable objects) { @@ -129,6 +134,14 @@ private void updateGraphVisibility() updateInfoMargin(); } + private void updateTimeVisibility() + { + info.FadeTo(ShowTime.Value ? 1 : 0, transition_duration, Easing.In); + + updateInfoMargin(); + } + + private void updateInfoMargin() { float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); From d01e76d9db3c4cffff803114edcbdf4c32dd6b5a Mon Sep 17 00:00:00 2001 From: ArijanJ Date: Fri, 9 Aug 2024 23:08:22 +0200 Subject: [PATCH 232/579] Fix double blank line --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 1586ac72a1e5..adb93c3ba3ca 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -89,7 +89,6 @@ protected override void LoadComplete() base.LoadComplete(); } - protected override void UpdateObjects(IEnumerable objects) { From fed5b9d7477f2118609b742c139c72aac800fd11 Mon Sep 17 00:00:00 2001 From: ArijanJ Date: Sat, 10 Aug 2024 09:45:30 +0200 Subject: [PATCH 233/579] Fix one more inspectcode warning --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index adb93c3ba3ca..6b2bb2b71836 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -140,7 +140,6 @@ private void updateTimeVisibility() updateInfoMargin(); } - private void updateInfoMargin() { float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); From 2233602184b725d4a468b53737f02d7788639c55 Mon Sep 17 00:00:00 2001 From: clayton Date: Sun, 11 Aug 2024 09:45:42 -0700 Subject: [PATCH 234/579] Update mania replay decode test to include 18K keypress --- .../Formats/LegacyScoreDecoderTest.cs | 9 ++++----- .../Resources/Replays/mania-replay.osr | Bin 1012 -> 1042 bytes 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 3759bfd0347c..070ade4ad9ae 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -19,7 +19,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -65,14 +65,13 @@ public void TestDecodeManiaReplay() Assert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore); Assert.AreEqual(3, score.ScoreInfo.MaxCombo); - Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic)); - Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL")); - Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL")); + Assert.That(score.ScoreInfo.APIMods.Select(m => m.Acronym), Is.EquivalentTo(new[] { "CL", "9K", "DS" })); Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001)); Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); - Assert.That(score.Replay.Frames, Is.Not.Empty); + Assert.That(score.Replay.Frames, Has.One.Matches(frame => + frame.Time == 414 && frame.Actions.SequenceEqual(new[] { ManiaAction.Key1, ManiaAction.Key16 }))); } } diff --git a/osu.Game.Tests/Resources/Replays/mania-replay.osr b/osu.Game.Tests/Resources/Replays/mania-replay.osr index da1a7bdd28e5b89eddd9742bce2b27bc8e75e6f5..ad55a5a31873acc943379b6391bb068ef92397b7 100644 GIT binary patch delta 941 zcmV;e15*6-2a*VoEL&k=VPRomVPRomVPRomVPRomVPRomVPRomVPRomVFCaE00000 z009610PCp?00RI3000033lK3dIX5zVF)T4SFgZAUF)U$Fy<3(_*a(UP003P80Du4` zM3IL|f2kp_Cwm-NDGQfLnv}R{A+5uM=V$!>%NF%V;(li`g9UYd{}pt#j-NjTL^Rx$ z)8vox9t#(tVgh24S9qwBpR*G=!suY`TLgA|2YbKlAt?C3gt0(^Ne~qSalq8ciz#p@2y$@@79OY<$k=i&@ z$%){vIws|+*G$%o*Abhy7QAvG3_AklP}%Uj6WtwNj^#D~Ap6s`y6)+@Vs|9!>rhF$ z(Th@AK+#MQ+@eBuu>#W^VSow$rY2vZvtyLf$Fe-%Y}I8;wFHWVyQB;i47XyCq#ga- zf6<@ARV=7N+nveRe(H&`|49MZB_STJbhkQ&Kg%tRZ5su!GX3k;q-nc^O3L)?dAyz6 zkq*PQof<5?`eqvXkU4Gq?CG7&!{XQ>l-81EDy%S!hcCnQx-Eg^znRa2m3+kl(&{_4 z|E#XY&k`FWw(AwgOBN}Z{pzUR1N`=ce>&ZHKn37E`?&xm+$MMK8G%yB-KT2|g&Q|v z!KGRky;bp!dh$;i3BgA{n8d#`oL{ZD{RXnE>twCR4liAX%w50Txmgdjn0*~7b8UJS z9`hlcqw752ctoxbZSXM@6|MMd(ofzlz~{ZqD%2&ADT$6q)$)FS?(9xRT2XjCf1zRl z=;!6R(l-}a+w+m{IJ5YVzyr}Z1|w6+wA>Y>tB-E9T=9J7eOSVn*3{NocS0mZaN9Iz z*8||a6~)(jJ?Q4h=*1y|Up7!MiC7a#ZYI3i;~I4y+@wb|cGLJW*H)1H2@k~Rna5gx zC}}6l4~U_i7R;**PnLpaABm`ie?{Ulv?yRhX^Ccj3l6au<|2+bfq5wf z{;YNi$U9q;a9n;o1AC&o44(&hq*5gqE1yu^<%DjP#j?h@8p1e)RU%gINB4K-`759> z6$9)02}Os4pSJ8KLxV_3sf!8h1%oh54Pr5{yWq7hhBpIMpL1g4;G#Hr7_gz8oO)mX P-u?|V0000000000tK-15 delta 911 zcmV;A191G32=oV#EL$>UWo0=sIWaXiGGjF`V>B^gIWRagV=^){GcsglGy(ts00000 z009610PCp?00RI3000003lK3dIX5zVF)T4SFgZAUF)U$Fy<3(_*a&O`003P803ZP5 zLy?C{f3PYVk96LhNin}dI^y)M#Q-6F;~hK6;6v0f4($qcC=D#w1v}gT>+Q44apIe> z^u{Hu`T^&O5+{?7)C=5^o4~{|a(=klR#xS2K1jz$5#VC2cu?g{{PKLP6wJ^#SieUk zU{__vBIA6}WQCuDj>EFa^^_#$Q4P>ae;2H4=ZGmx7w}kC2MuRN0?rGS(8}Y5 zhz;zQ^pgdCFAA95G5naWn3}GRN*enPdqN;!X`#^cNS3(>dQ8HnIdQ)YOF?B;rDi!z~! ze>k`!wUtQ?QkF@%v-^b`=F}eWcIcyuEA?+EC5nJ6&VHDlo)zLnjA}iYry^Wc282uw zb8MhH5)cj#aw!_hD@mb6mKMjw{o@f8;PD*nHwXocs~eYj)Vr=h%$l3Zu|vmp^tLml zymcsXvkSx^NtzJrIgdT3GIkW!%>zeQe|zJn8w6pYSsUr1=*o_Q#S)6A-dHA@d9~(^0+ax|D@!;n;Z|| zFvVlqvtKZJf2x8EVoS#`3YPzS-*vaun`i1N*BG`MvK_+XgTPh^GgHpdy!e==e?Np^ zP79t-TF}QHA=p$CMZ8h|*&agcSs?d99UKOtBUW5%$#uK?C(WYoiHQt9+}D}2*H`GI z+MHqHE17uF?Dqy%$~lzbk4H;u{VzwEFmdj}XdWCiMA_1-@_?MecxP^r?UDsYE^qoo z$|(tSLa2UCM@zI7zrxT> Date: Sun, 11 Aug 2024 09:45:43 -0700 Subject: [PATCH 235/579] Fix mouseX legacy replay parsing for high key counts in mania --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index ba1fdd6adfca..6ad118547ba0 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -280,8 +280,11 @@ private void readLegacyReplay(Replay replay, StreamReader reader) continue; } + // In mania, mouseX encodes the pressed keys in the lower 20 bits + int mouseXParseLimit = currentRuleset.RulesetInfo.OnlineID == 3 ? (1 << 20) - 1 : Parsing.MAX_COORDINATE_VALUE; + float diff = Parsing.ParseFloat(split[0]); - float mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE); + float mouseX = Parsing.ParseFloat(split[1], mouseXParseLimit); float mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE); lastTime += diff; From d2eb6ccb8caa7455d1daba4ca9d2518ecbec4ed5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2024 14:27:21 +0900 Subject: [PATCH 236/579] Standardise skin transformer code structure --- .../Legacy/CatchLegacySkinTransformer.cs | 51 ++++++------- .../Legacy/OsuLegacySkinTransformer.cs | 71 ++++++++++--------- 2 files changed, 65 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index df8c04638d5c..4efdafc0345f 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -31,10 +31,7 @@ public CatchLegacySkinTransformer(ISkin skin) switch (lookup) { case SkinComponentsContainerLookup containerLookup: - if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) - return base.GetDrawableComponent(lookup); - - // Modifications for global components. + // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -43,27 +40,33 @@ public CatchLegacySkinTransformer(ISkin skin) return d; // Our own ruleset components default. - // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. - return new DefaultSkinComponentsContainer(container => + switch (containerLookup.Target) { - var keyCounter = container.OfType().FirstOrDefault(); - - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - }) - { - Children = new Drawable[] - { - new LegacyKeyCounterDisplay(), - } - }; + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); + + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + }) + { + Children = new Drawable[] + { + new LegacyKeyCounterDisplay(), + } + }; + } + + return null; case CatchSkinComponentLookup catchSkinComponent: switch (catchSkinComponent.Component) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index c2381fff88e6..aad8ffb1cf74 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -45,9 +45,6 @@ public OsuLegacySkinTransformer(ISkin skin) switch (lookup) { case SkinComponentsContainerLookup containerLookup: - if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) - return base.GetDrawableComponent(lookup); - // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -56,42 +53,50 @@ public OsuLegacySkinTransformer(ISkin skin) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; - return new DefaultSkinComponentsContainer(container => + // Our own ruleset components default. + switch (containerLookup.Target) { - var keyCounter = container.OfType().FirstOrDefault(); - - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - - var combo = container.OfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.BottomLeft; - combo.Origin = Anchor.BottomLeft; - combo.Scale = new Vector2(1.28f); - } - }) - { - Children = new Drawable[] - { - new LegacyDefaultComboCounter(), - new LegacyKeyCounterDisplay(), - } - }; + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); + + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); + } + }) + { + Children = new Drawable[] + { + new LegacyDefaultComboCounter(), + new LegacyKeyCounterDisplay(), + } + }; + } + + return null; case OsuSkinComponentLookup osuComponent: switch (osuComponent.Component) { case OsuSkinComponents.FollowPoint: - return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS)); + return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, + maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS)); case OsuSkinComponents.SliderScorePoint: return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS); From 306e84c7aca8ac5dc89be8fb0f15941902ac05f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Aug 2024 10:46:58 +0200 Subject: [PATCH 237/579] Move disposal method to more expected location --- .../OnlinePlay/DailyChallenge/DailyChallengeIntro.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 83ea0f8088af..e59031f66355 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -367,12 +367,6 @@ public override void OnSuspending(ScreenTransitionEvent e) base.OnSuspending(e); } - protected override void Dispose(bool isDisposing) - { - resetAudio(); - base.Dispose(isDisposing); - } - private void updateAnimationState() { if (!beatmapBackgroundLoaded || !this.IsCurrentScreen()) @@ -500,6 +494,12 @@ private void playBeatmapImpactSample() beatmapImpactChannel?.Play(); } + protected override void Dispose(bool isDisposing) + { + resetAudio(); + base.Dispose(isDisposing); + } + private void resetAudio() { dateWindupChannel?.Stop(); From 041c70e4ebb8065ef5ab0b866ad17991c345319a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Aug 2024 11:19:02 +0200 Subject: [PATCH 238/579] Fix tests --- osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs | 1 + .../Visual/UserInterface/TestSceneMainMenuButton.cs | 10 ++++++++-- osu.Game/Screens/Menu/DailyChallengeButton.cs | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 613d8347b7b4..aab3716463d7 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -48,6 +48,7 @@ public void TestDailyChallenge() { new PlaylistItem(beatmap) }, + StartDate = { Value = DateTimeOffset.Now.AddMinutes(-30) }, EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) } }); return true; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index af98aa21db90..98f2b129ff4a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -58,6 +58,7 @@ public void TestDailyChallengeButton() { new PlaylistItem(beatmap) }, + StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) }, EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } }); return true; @@ -95,8 +96,13 @@ public void TestDailyChallengeButton() }, }; }); - AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1)); + AddStep("clear notifications", () => + { + foreach (var notification in notificationOverlay.AllNotifications) + notification.Close(runFlingAnimation: false); + }); AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); @@ -105,7 +111,7 @@ public void TestDailyChallengeButton() { RoomID = 1234, })); - AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1)); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); } } } diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index ac04afdc4d7a..2357c60f3def 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -154,7 +154,9 @@ private void dailyChallengeChanged(ValueChangedEvent info) cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; // We only want to notify the user if a new challenge recently went live. - if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600 && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) + if (room.StartDate.Value != null + && Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600 + && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) { lastNotifiedDailyChallengeRoomId = room.RoomID.Value; notificationOverlay?.Post(new NewDailyChallengeNotification(room)); From 54a1d791362c31da283ca3afd33a3841e440e92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Aug 2024 11:23:36 +0200 Subject: [PATCH 239/579] Clean up some naming weirdness --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 2357c60f3def..234c4c27d51c 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -132,21 +132,21 @@ protected override void Update() private long? lastNotifiedDailyChallengeRoomId; - private void dailyChallengeChanged(ValueChangedEvent info) + private void dailyChallengeChanged(ValueChangedEvent _) { UpdateState(); scheduledCountdownUpdate?.Cancel(); scheduledCountdownUpdate = null; - if (this.info.Value == null) + if (info.Value == null) { Room = null; cover.OnlineInfo = TooltipContent = null; } else { - var roomRequest = new GetRoomRequest(this.info.Value.Value.RoomID); + var roomRequest = new GetRoomRequest(info.Value.Value.RoomID); roomRequest.Success += room => { From 96bd374b18fa12df7545f0aeca4660147e0feec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Aug 2024 11:24:59 +0200 Subject: [PATCH 240/579] Change notification interval to 30 minutes --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 234c4c27d51c..e19ba6612c7c 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -155,7 +155,7 @@ private void dailyChallengeChanged(ValueChangedEvent _) // We only want to notify the user if a new challenge recently went live. if (room.StartDate.Value != null - && Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600 + && Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800 && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) { lastNotifiedDailyChallengeRoomId = room.RoomID.Value; From b567ab2a3923d6579cf23849447546e704a621fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2024 20:28:21 +0900 Subject: [PATCH 241/579] Fix context menus sometimes not being clickable at song select Closes https://github.com/ppy/osu/issues/21602. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +-- osu.Game/Screens/Select/SongSelect.cs | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index cd0d2eea2c5e..b0f198d48634 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -22,7 +22,6 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; using osuTK; @@ -209,7 +208,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) public BeatmapCarousel() { root = new CarouselRoot(this); - InternalChild = new OsuContextMenuContainer + InternalChild = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 307043a31251..2ee5a6f3cbf2 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -26,6 +26,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -1081,7 +1082,7 @@ public VerticalMaskingContainer() Anchor = Anchor.Centre; Origin = Anchor.Centre; Width = panel_overflow; // avoid horizontal masking so the panels don't clip when screen stack is pushed. - InternalChild = Content = new Container + InternalChild = Content = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, From 7cbb4ab6f1c689f7f57d1ca6f046d4b49cdd1d65 Mon Sep 17 00:00:00 2001 From: CloneWith Date: Tue, 13 Aug 2024 11:50:33 +0800 Subject: [PATCH 242/579] Get in-game locale for wiki pages --- osu.Game/Overlays/WikiOverlay.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index ffbc168fb7d3..88450ea6db80 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Extensions; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -38,11 +39,20 @@ public partial class WikiOverlay : OnlineOverlay private WikiArticlePage articlePage; + private Bindable language; + public WikiOverlay() : base(OverlayColourScheme.Orange, false) { } + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + // Fetch current language on load for translated pages (if possible) + language = game.CurrentLanguage.GetBoundCopy(); + } + public void ShowPage(string pagePath = INDEX_PATH) { path.Value = pagePath.Trim('/'); @@ -113,12 +123,13 @@ private void onPathChanged(ValueChangedEvent e) cancellationToken?.Cancel(); request?.Cancel(); + // Language code + path, or just path1 + path2 in case string[] values = e.NewValue.Split('/', 2); - if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var language)) - request = new GetWikiRequest(values[1], language); + if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var lang)) + request = new GetWikiRequest(values[1], lang); else - request = new GetWikiRequest(e.NewValue); + request = new GetWikiRequest(e.NewValue, language.Value); Loading.Show(); From 12a1889fac182cf521dc87693fb9a6c0e7ecb636 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 14:03:26 +0900 Subject: [PATCH 243/579] Use key offsets instead of cast --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index a5cc94ea9afd..5d4cebca3011 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -38,11 +38,11 @@ protected override void GenerateFrames() switch (point) { case HitPoint: - actions.Add((ManiaAction)point.Column); + actions.Add(ManiaAction.Key1 + point.Column); break; case ReleasePoint: - actions.Remove((ManiaAction)point.Column); + actions.Remove(ManiaAction.Key1 + point.Column); break; } } From ca91726190636314d46b9bddd6f865dd870781a2 Mon Sep 17 00:00:00 2001 From: CloneWith Date: Tue, 13 Aug 2024 13:34:11 +0800 Subject: [PATCH 244/579] Reload wiki page on language change --- osu.Game/Overlays/WikiOverlay.cs | 39 +++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 88450ea6db80..058783253324 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -68,7 +68,9 @@ public void ShowPage(string pagePath = INDEX_PATH) protected override void LoadComplete() { base.LoadComplete(); + path.BindValueChanged(onPathChanged); + language.BindValueChanged(onLangChanged); wikiData.BindTo(Header.WikiPageData); } @@ -110,26 +112,18 @@ protected override void UpdateAfterChildren() } } - private void onPathChanged(ValueChangedEvent e) + private void loadPage(string path, Language lang) { - // the path could change as a result of redirecting to a newer location of the same page. - // we already have the correct wiki data, so we can safely return here. - if (e.NewValue == wikiData.Value?.Path) - return; - - if (e.NewValue == "error") - return; - cancellationToken?.Cancel(); request?.Cancel(); // Language code + path, or just path1 + path2 in case - string[] values = e.NewValue.Split('/', 2); + string[] values = path.Split('/', 2); - if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var lang)) - request = new GetWikiRequest(values[1], lang); + if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var parsedLang)) + request = new GetWikiRequest(values[1], parsedLang); else - request = new GetWikiRequest(e.NewValue, language.Value); + request = new GetWikiRequest(path, lang); Loading.Show(); @@ -143,6 +137,25 @@ private void onPathChanged(ValueChangedEvent e) api.PerformAsync(request); } + private void onPathChanged(ValueChangedEvent e) + { + // the path could change as a result of redirecting to a newer location of the same page. + // we already have the correct wiki data, so we can safely return here. + if (e.NewValue == wikiData.Value?.Path) + return; + + if (e.NewValue == "error") + return; + + loadPage(e.NewValue, language.Value); + } + + private void onLangChanged(ValueChangedEvent e) + { + // Path unmodified, just reload the page with new language value. + loadPage(path.Value, e.NewValue); + } + private void onSuccess(APIWikiPage response) { wikiData.Value = response; From 952a73b005e83eaea0167790462d78447eeba195 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 15:51:33 +0900 Subject: [PATCH 245/579] Make `ManiaAction` start at `1` --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 36ccf68d76cb..9c58139c3754 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -20,7 +20,7 @@ public ManiaInputManager(RulesetInfo ruleset, int variant) public enum ManiaAction { [Description("Key 1")] - Key1, + Key1 = 1, [Description("Key 2")] Key2, From 3f02869bcc7455357910f716aa1f5841fda7c2ac Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 16:08:06 +0900 Subject: [PATCH 246/579] Enable NRT while we're here --- osu.Game/Overlays/WikiOverlay.cs | 40 +++++++++++++------------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 058783253324..14a25a909db6 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.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.Linq; using System.Threading; @@ -25,32 +23,35 @@ public partial class WikiOverlay : OnlineOverlay public string CurrentPath => path.Value; private readonly Bindable path = new Bindable(INDEX_PATH); - - private readonly Bindable wikiData = new Bindable(); + private readonly Bindable wikiData = new Bindable(); + private readonly IBindable language = new Bindable(); [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private GetWikiRequest request; + [Resolved] + private OsuGameBase game { get; set; } = null!; - private CancellationTokenSource cancellationToken; + private GetWikiRequest? request; + private CancellationTokenSource? cancellationToken; + private WikiArticlePage? articlePage; private bool displayUpdateRequired = true; - private WikiArticlePage articlePage; - - private Bindable language; - public WikiOverlay() : base(OverlayColourScheme.Orange, false) { } - [BackgroundDependencyLoader] - private void load(OsuGameBase game) + protected override void LoadComplete() { - // Fetch current language on load for translated pages (if possible) - language = game.CurrentLanguage.GetBoundCopy(); + base.LoadComplete(); + + path.BindValueChanged(onPathChanged); + wikiData.BindTo(Header.WikiPageData); + + language.BindTo(game.CurrentLanguage); + language.BindValueChanged(onLangChanged); } public void ShowPage(string pagePath = INDEX_PATH) @@ -65,15 +66,6 @@ public void ShowPage(string pagePath = INDEX_PATH) ShowParentPage = showParentPage, }; - protected override void LoadComplete() - { - base.LoadComplete(); - - path.BindValueChanged(onPathChanged); - language.BindValueChanged(onLangChanged); - wikiData.BindTo(Header.WikiPageData); - } - protected override void PopIn() { base.PopIn(); From 4f37643780d18049a384917debbe3ceed2c8d926 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 16:38:56 +0900 Subject: [PATCH 247/579] Revert "Make `ManiaAction` start at `1`" This reverts commit 952a73b005e83eaea0167790462d78447eeba195. --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 9c58139c3754..36ccf68d76cb 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -20,7 +20,7 @@ public ManiaInputManager(RulesetInfo ruleset, int variant) public enum ManiaAction { [Description("Key 1")] - Key1 = 1, + Key1, [Description("Key 2")] Key2, From 58354e3e6841b2a2c44be1cc34a0dfdb40455151 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 17:18:11 +0900 Subject: [PATCH 248/579] Fix another test The last two PRs didn't interact well together. --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 070ade4ad9ae..713f2f3fb13e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -71,7 +71,7 @@ public void TestDecodeManiaReplay() Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); Assert.That(score.Replay.Frames, Has.One.Matches(frame => - frame.Time == 414 && frame.Actions.SequenceEqual(new[] { ManiaAction.Key1, ManiaAction.Key16 }))); + frame.Time == 414 && frame.Actions.SequenceEqual(new[] { ManiaAction.Key1, ManiaAction.Key18 }))); } } From 14a00621f8280a82449e843dd1817f06889e391f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 17:28:16 +0900 Subject: [PATCH 249/579] Fix occasional test failures in `TestSceneBetmapRecommendations` The game was being constructed befor the API was setup, which could mean depending on test execution ordering and speed, the recommendations array would not be filled. Easy to reproduce by `[Solo]`ing `TestCorrectStarRatingIsUsed`. See https://github.com/ppy/osu/runs/28689915929#r0s0. --- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index a368e901f53d..16c8bc1a6b54 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -31,8 +31,6 @@ public partial class TestSceneBeatmapRecommendations : OsuGameTestScene [SetUpSteps] public override void SetUpSteps() { - base.SetUpSteps(); - AddStep("populate ruleset statistics", () => { Dictionary rulesetStatistics = new Dictionary(); @@ -68,6 +66,8 @@ decimal getNecessaryPP(int? rulesetID) return 0; } } + + base.SetUpSteps(); } [Test] From 93c1a27f226101b37685ff67166d01d71982f43e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 18:34:15 +0900 Subject: [PATCH 250/579] Add failing test --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index b03fa00f76da..3eff7ca0172a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -248,7 +248,8 @@ public void TestHitLightingColour() { AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true)); AddStep("catch fruit", () => attemptCatch(new Fruit())); - AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType().First()?.Entry?.ObjectColour == this.ChildrenOfType().First().AccentColour.Value); + AddAssert("correct hit lighting colour", + () => catcher.ChildrenOfType().First()?.Entry?.ObjectColour == this.ChildrenOfType().First().AccentColour.Value); } [Test] @@ -259,6 +260,16 @@ public void TestHitLightingDisabled() AddAssert("no hit lighting", () => !catcher.ChildrenOfType().Any()); } + [Test] + public void TestAllExplodedObjectsAtUniquePositions() + { + AddStep("catch normal fruit", () => attemptCatch(new Fruit())); + AddStep("catch normal fruit", () => attemptCatch(new Fruit { IndexInBeatmap = 2, LastInCombo = true })); + AddAssert("two fruit at distinct x coordinates", + () => this.ChildrenOfType().Select(f => f.DrawPosition.X).Distinct(), + () => Has.Exactly(2).Items); + } + private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count); private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state); From fbfe3a488744fea1137f315fd518c17e5a6d5c8a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 18:07:52 +0900 Subject: [PATCH 251/579] Fix fruit positions getting mangled when exploded --- .../Objects/Drawables/CaughtObject.cs | 28 ++++++------- .../DrawablePalpableCatchHitObject.cs | 4 ++ .../Objects/Drawables/IHasCatchObjectState.cs | 34 +++++++++++---- osu.Game.Rulesets.Catch/UI/Catcher.cs | 42 ++++++++++++------- 4 files changed, 69 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs index 0c26c5217182..b76e0e9bae1b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs @@ -21,11 +21,9 @@ public abstract partial class CaughtObject : SkinnableDrawable, IHasCatchObjectS public Bindable AccentColour { get; } = new Bindable(); public Bindable HyperDash { get; } = new Bindable(); public Bindable IndexInBeatmap { get; } = new Bindable(); - + public Vector2 DisplayPosition => DrawPosition; public Vector2 DisplaySize => Size * Scale; - public float DisplayRotation => Rotation; - public double DisplayStartTime => HitObject.StartTime; /// @@ -44,19 +42,6 @@ protected CaughtObject(CatchSkinComponents skinComponent, Func - /// Copies the hit object visual state from another object. - /// - public virtual void CopyStateFrom(IHasCatchObjectState objectState) - { - HitObject = objectState.HitObject; - Scale = Vector2.Divide(objectState.DisplaySize, Size); - Rotation = objectState.DisplayRotation; - AccentColour.Value = objectState.AccentColour.Value; - HyperDash.Value = objectState.HyperDash.Value; - IndexInBeatmap.Value = objectState.IndexInBeatmap.Value; - } - protected override void FreeAfterUse() { ClearTransforms(); @@ -64,5 +49,16 @@ protected override void FreeAfterUse() base.FreeAfterUse(); } + + public void RestoreState(CatchObjectState state) + { + HitObject = state.HitObject; + AccentColour.Value = state.AccentColour; + HyperDash.Value = state.HyperDash; + IndexInBeatmap.Value = state.IndexInBeatmap; + Position = state.DisplayPosition; + Scale = Vector2.Divide(state.DisplaySize, Size); + Rotation = state.DisplayRotation; + } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index ade00918ab09..2919f6996642 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -37,6 +37,8 @@ public abstract partial class DrawablePalpableCatchHitObject : DrawableCatchHitO /// protected readonly Container ScalingContainer; + public Vector2 DisplayPosition => DrawPosition; + public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale; public float DisplayRotation => ScalingContainer.Rotation; @@ -95,5 +97,7 @@ protected override void OnFree() base.OnFree(); } + + public void RestoreState(CatchObjectState state) => throw new NotSupportedException("Cannot restore state into a drawable catch hitobject."); } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs index 18fc0db6e36f..e4a67d8fbfa5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs @@ -13,17 +13,37 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public interface IHasCatchObjectState { PalpableCatchHitObject HitObject { get; } - - double DisplayStartTime { get; } - Bindable AccentColour { get; } - Bindable HyperDash { get; } - Bindable IndexInBeatmap { get; } - + double DisplayStartTime { get; } + Vector2 DisplayPosition { get; } Vector2 DisplaySize { get; } - float DisplayRotation { get; } + + void RestoreState(CatchObjectState state); + } + + public static class HasCatchObjectStateExtensions + { + public static CatchObjectState SaveState(this IHasCatchObjectState target) => new CatchObjectState( + target.HitObject, + target.AccentColour.Value, + target.HyperDash.Value, + target.IndexInBeatmap.Value, + target.DisplayStartTime, + target.DisplayPosition, + target.DisplaySize, + target.DisplayRotation); } + + public readonly record struct CatchObjectState( + PalpableCatchHitObject HitObject, + Color4 AccentColour, + bool HyperDash, + int IndexInBeatmap, + double DisplayStartTime, + Vector2 DisplayPosition, + Vector2 DisplaySize, + float DisplayRotation); } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index dca01fc61a4e..6a1b251d6016 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Buffers; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -362,7 +363,7 @@ private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Ve if (caughtObject == null) return; - caughtObject.CopyStateFrom(drawableObject); + caughtObject.RestoreState(drawableObject.SaveState()); caughtObject.Anchor = Anchor.TopCentre; caughtObject.Position = position; caughtObject.Scale *= caught_fruit_scale_adjust; @@ -411,41 +412,50 @@ private void addLighting(JudgementResult judgementResult, Color4 colour, float x } } - private CaughtObject getDroppedObject(CaughtObject caughtObject) + private CaughtObject getDroppedObject(CatchObjectState state) { - var droppedObject = getCaughtObject(caughtObject.HitObject); + var droppedObject = getCaughtObject(state.HitObject); Debug.Assert(droppedObject != null); - droppedObject.CopyStateFrom(caughtObject); + droppedObject.RestoreState(state); droppedObject.Anchor = Anchor.TopLeft; - droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget); + droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(state.DisplayPosition, droppedObjectTarget); return droppedObject; } private void clearPlate(DroppedObjectAnimation animation) { - var caughtObjects = caughtObjectContainer.Children.ToArray(); + int caughtCount = caughtObjectContainer.Children.Count; + CatchObjectState[] states = ArrayPool.Shared.Rent(caughtCount); - caughtObjectContainer.Clear(false); - - // Use the already returned PoolableDrawables for new objects - var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray(); + try + { + for (int i = 0; i < caughtCount; i++) + states[i] = caughtObjectContainer.Children[i].SaveState(); - droppedObjectTarget.AddRange(droppedObjects); + caughtObjectContainer.Clear(false); - foreach (var droppedObject in droppedObjects) - applyDropAnimation(droppedObject, animation); + for (int i = 0; i < caughtCount; i++) + { + CaughtObject obj = getDroppedObject(states[i]); + droppedObjectTarget.Add(obj); + applyDropAnimation(obj, animation); + } + } + finally + { + ArrayPool.Shared.Return(states); + } } private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation) { + CatchObjectState state = caughtObject.SaveState(); caughtObjectContainer.Remove(caughtObject, false); - var droppedObject = getDroppedObject(caughtObject); - + var droppedObject = getDroppedObject(state); droppedObjectTarget.Add(droppedObject); - applyDropAnimation(droppedObject, animation); } From c9b2a5bb9cc86d22d3731ad3375a734e36dcf219 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Aug 2024 13:01:50 +0300 Subject: [PATCH 252/579] Fix user profile overlay colour resetting when changing rulesets --- osu.Game/Overlays/UserProfileOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index ac1fc44cd6d7..076905819e2c 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -96,7 +96,8 @@ private void fetchAndSetContent() { Debug.Assert(user != null); - if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) + bool sameUser = user.OnlineID == Header.User.Value?.User.Id; + if (sameUser && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) return; if (sectionsContainer != null) @@ -118,7 +119,9 @@ private void fetchAndSetContent() } : Array.Empty(); - changeOverlayColours(OverlayColourScheme.Pink.GetHue()); + if (!sameUser) + changeOverlayColours(OverlayColourScheme.Pink.GetHue()); + recreateBaseContent(); if (API.State.Value != APIState.Offline) From 7acc1772cbef1116160caabdba787015d9ac6bff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Aug 2024 13:07:21 +0300 Subject: [PATCH 253/579] Add test coverage --- .../Online/TestSceneUserProfileOverlay.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 3bb38f167f2c..006610dccd06 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -11,6 +11,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Rulesets.Taiko; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -192,13 +193,26 @@ public void TestCustomColourSchemeWithReload() int hue2 = 0; AddSliderStep("hue 2", 0, 360, 50, h => hue2 = h); - AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 2 })); AddWaitStep("wait some", 3); AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser { Username = $"Colorful #{hue2}", - Id = 1, + Id = 2, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + ProfileHue = hue2, + PlayMode = "osu", + })); + + AddStep("show user different ruleset", () => profile.ShowUser(new APIUser { Id = 2 }, new TaikoRuleset().RulesetInfo)); + AddWaitStep("wait some", 3); + + AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser + { + Username = $"Colorful #{hue2}", + Id = 2, CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", ProfileHue = hue2, From 69c5e6a799175cdc781c30724368186f7e788580 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 19:52:14 +0900 Subject: [PATCH 254/579] Remove unused property causing CI inspection I don't see this in my Rider locally. I suppose we can remove it, though it was intentionally added so that the struct mirrors the interface. --- .../Objects/Drawables/IHasCatchObjectState.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs index e4a67d8fbfa5..19e66bf995b7 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs @@ -31,7 +31,6 @@ public static class HasCatchObjectStateExtensions target.AccentColour.Value, target.HyperDash.Value, target.IndexInBeatmap.Value, - target.DisplayStartTime, target.DisplayPosition, target.DisplaySize, target.DisplayRotation); @@ -42,7 +41,6 @@ public readonly record struct CatchObjectState( Color4 AccentColour, bool HyperDash, int IndexInBeatmap, - double DisplayStartTime, Vector2 DisplayPosition, Vector2 DisplaySize, float DisplayRotation); From d74ac57092f3d1953345532010936d93f3beb052 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 19:26:24 +0900 Subject: [PATCH 255/579] Never call `prepareDrawables` from unsafe context I can't mentally figure out *what* is causing the issue here, but in the case where `prepareDrawables` is called from `JudgementBody.OnSkinChanged` (only happens in a non-pooled scenario), things go very wrong. I think a smell test is enough for anyone to agree that the flow was very bad. Removing this call doesn't seem to cause any issues. `runAnimation` should always be called in `PrepareForUse` (both pooled and non-pooled scenarios) so things should still always be in a correct state. Closes #29398. --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index bdeadfd201b6..1b12bfc945d5 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -119,9 +119,6 @@ protected override void PrepareForUse() private void runAnimation() { - // is a no-op if the drawables are already in a correct state. - prepareDrawables(); - // undo any transforms applies in ApplyMissAnimations/ApplyHitAnimations to get a sane initial state. ApplyTransformsAt(double.MinValue, true); ClearTransforms(true); From bb0c9e24974f2a8ea2b3c2954d2342f053a7da7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 19:30:29 +0900 Subject: [PATCH 256/579] Add log output when judgements aren't being pooled --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 1b12bfc945d5..0e04cd3eec5d 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -112,6 +113,9 @@ protected override void PrepareForUse() { base.PrepareForUse(); + if (!IsInPool) + Logger.Log($"{nameof(DrawableJudgement)} for judgement type {Result} was not retrieved from a pool. Consider adding to a JudgementPooler."); + Debug.Assert(Result != null); runAnimation(); From 2221c4891f3fa7e9d0b005adefee982ab6091f2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 21:03:00 +0900 Subject: [PATCH 257/579] Remove legacy non-pooled pathway to `DrawableJudgement` --- .../Skinning/TestSceneDrawableJudgement.cs | 16 +++++++++++----- .../UI/DrawableManiaJudgement.cs | 10 ---------- .../Rulesets/Judgements/DrawableJudgement.cs | 11 ----------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index c993ba0e0a91..b52919987f15 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -28,14 +28,20 @@ public TestSceneDrawableJudgement() AddStep("Show " + result.GetDescription(), () => { SetContents(_ => - new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()) - { - Type = result - }, null) + { + var drawableManiaJudgement = new DrawableManiaJudgement { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }); + }; + + drawableManiaJudgement.Apply(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()) + { + Type = result + }, null); + + return drawableManiaJudgement; + }); // for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value // (see `LegacyManiaJudgementPiece.load()`). diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index 896dfb2b23ba..9f25a44e2177 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -5,22 +5,12 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.UI { public partial class DrawableManiaJudgement : DrawableJudgement { - public DrawableManiaJudgement(JudgementResult result, DrawableHitObject judgedObject) - : base(result, judgedObject) - { - } - - public DrawableManiaJudgement() - { - } - protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result); private partial class DefaultManiaJudgementPiece : DefaultJudgementPiece diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 0e04cd3eec5d..8c326ecf4946 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -36,17 +36,6 @@ public partial class DrawableJudgement : PoolableDrawable private readonly Lazy proxiedAboveHitObjectsContent; public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value; - /// - /// Creates a drawable which visualises a . - /// - /// The judgement to visualise. - /// The object which was judged. - public DrawableJudgement(JudgementResult result, DrawableHitObject judgedObject) - : this() - { - Apply(result, judgedObject); - } - public DrawableJudgement() { Size = new Vector2(judgement_size); From 78ef436ea085c1e08258cf46a0610677af729af4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 12:23:47 +0900 Subject: [PATCH 258/579] Update test debug output to test second scenario --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ee2f1d64dcf5..d2c69c2ceb57 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -788,7 +788,7 @@ public Task PlaylistItemChanged(MultiplayerPlaylistItem item) try { - indexOf = Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID)); + indexOf = APIRoom!.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); Room.Playlist[indexOf] = item; } catch From dd9705b660d54d75ee8db01269aed840d6c93bb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 12:26:21 +0900 Subject: [PATCH 259/579] Fix file access test failure by forcing retries See https://github.com/ppy/osu/actions/runs/10369630825/job/28708248682. --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 3ae1d9786d06..2b2358198443 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -47,6 +47,7 @@ using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Utils; using osuTK; using osuTK.Input; using SharpCompress; @@ -240,11 +241,14 @@ public void TestAttemptPlayBeatmapWrongHashFails() AddStep("change beatmap files", () => { - foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) + FileUtils.AttemptOperation(() => { - using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite)) - stream.WriteByte(0); - } + foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) + { + using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite)) + stream.WriteByte(0); + } + }); }); AddStep("invalidate cache", () => From f882ad4a53c4beb0521f57cadd7e969e753b69b6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 14 Aug 2024 15:10:55 +0900 Subject: [PATCH 260/579] Add localisation for daily challenge day/week units --- .../DailyChallengeStatsDisplayStrings.cs | 24 +++++++++++++++++++ .../Components/DailyChallengeStatsDisplay.cs | 4 ++-- .../Components/DailyChallengeStatsTooltip.cs | 13 +++++----- 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs diff --git a/osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs b/osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs new file mode 100644 index 000000000000..2ef5e45c923c --- /dev/null +++ b/osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs @@ -0,0 +1,24 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class DailyChallengeStatsDisplayStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.DailyChallengeStatsDisplay"; + + /// + /// "{0}d" + /// + public static LocalisableString UnitDay(LocalisableString count) => new TranslatableString(getKey(@"unit_day"), @"{0}d", count); + + /// + /// "{0}w" + /// + public static LocalisableString UnitWeek(LocalisableString count) => new TranslatableString(getKey(@"unit_week"), @"{0}w", count); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index f55eb595d7fd..82d3cfafd711 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -12,8 +12,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; +using osu.Game.Localisation; namespace osu.Game.Overlays.Profile.Header.Components { @@ -106,7 +106,7 @@ private void updateDisplay() APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics; - dailyPlayCount.Text = UsersStrings.ShowDailyChallengeUnitDay(stats.PlayCount.ToLocalisableString("N0")); + dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0")); dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount)); TooltipContent = new DailyChallengeTooltipData(colourProvider, stats); diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index 1b54633b8a9d..64a8d67c5bcf 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -9,15 +9,16 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; using osuTK; -using Box = osu.Framework.Graphics.Shapes.Box; -using Color4 = osuTK.Graphics.Color4; +using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header.Components { @@ -112,16 +113,16 @@ public void SetContent(DailyChallengeTooltipData content) background.Colour = colourProvider.Background4; topBackground.Colour = colourProvider.Background5; - currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); + currentDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); - currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); + currentWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent)); - bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); + bestDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest)); - bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); + bestWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest)); topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0"); From 7c142bcedf0877a05b9f899b0648cac6b925d354 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 15:12:23 +0900 Subject: [PATCH 261/579] Fix incorrect anchor handling in `ArgonManiaComboCounter` --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index e77650bed140..43d4e89cdbfd 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -30,8 +30,12 @@ protected override void LoadComplete() private void updateAnchor() { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction + if (!Anchor.HasFlag(Anchor.y1)) + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + } // since we flip the vertical anchor when changing scroll direction, // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. From 46d41cb59083eaf60c3c5c2b35a05eea7aa9d55b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 12 Aug 2024 20:01:42 -0700 Subject: [PATCH 262/579] Add base song select components test scene --- .../SongSelect/TestSceneSongSelectV2.cs | 5 +-- .../TestSceneSongSelectV2Navigation.cs | 3 +- .../SongSelectComponentsTestScene.cs | 45 +++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs index 0a632793cc2c..02f503d4330a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs @@ -17,7 +17,6 @@ using osu.Game.Screens; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; -using osu.Game.Screens.SelectV2; using osu.Game.Screens.SelectV2.Footer; using osuTK.Input; @@ -63,8 +62,8 @@ public override void SetUpSteps() { base.SetUpSteps(); - AddStep("load screen", () => Stack.Push(new SongSelectV2())); - AddUntilStep("wait for load", () => Stack.CurrentScreen is SongSelectV2 songSelect && songSelect.IsLoaded); + AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SongSelectV2())); + AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelectV2 songSelect && songSelect.IsLoaded); } #region Footer diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs index ededb802284c..0ca27c539af1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Screens.Menu; -using osu.Game.Screens.SelectV2; using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect @@ -17,7 +16,7 @@ public override void SetUpSteps() base.SetUpSteps(); AddStep("press enter", () => InputManager.Key(Key.Enter)); AddWaitStep("wait", 5); - PushAndConfirm(() => new SongSelectV2()); + PushAndConfirm(() => new Screens.SelectV2.SongSelectV2()); } [Test] diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs new file mode 100644 index 000000000000..ff81d72d1277 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public abstract partial class SongSelectComponentsTestScene : OsuTestScene + { + [Cached] + protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + /// + /// The beatmap. Can be local/online depending on the context. + /// + [Cached(typeof(IBindable))] + protected readonly Bindable BeatmapInfo = new Bindable(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + // mimics song select's `WorkingBeatmap` binding + Beatmap.BindValueChanged(b => + { + BeatmapInfo.Value = b.NewValue.BeatmapInfo; + }); + } + + [SetUpSteps] + public virtual void SetUpSteps() + { + AddStep("reset dependencies", () => + { + Beatmap.SetDefault(); + SelectedMods.SetDefault(); + BeatmapInfo.Value = null; + }); + } + } +} From 625c6fc7eb9c932d167ceb4b2679a1cb2e7758d4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 12 Aug 2024 19:52:42 -0700 Subject: [PATCH 263/579] Implement song select v2 difficulty name content component --- .../TestSceneDifficultyNameContent.cs | 97 ++++++++++++++++++ .../SelectV2/Wedge/DifficultyNameContent.cs | 98 +++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs create mode 100644 osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs new file mode 100644 index 000000000000..884dae16176a --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.SelectV2.Wedge; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestScene + { + private Container? content; + private DifficultyNameContent? difficultyNameContent; + private float relativeWidth; + + [BackgroundDependencyLoader] + private void load() + { + AddSliderStep("change relative width", 0, 1f, 0.5f, v => + { + if (content != null) + content.Width = v; + + relativeWidth = v; + }); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("set content", () => + { + Child = content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Width = relativeWidth, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background5, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Child = difficultyNameContent = new DifficultyNameContent(), + } + } + }; + }); + } + + [Test] + public void TestLocalBeatmap() + { + AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); + AddAssert("author is not set", () => !difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Any()); + + AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + DifficultyName = "really long difficulty name that gets truncated", + Metadata = new BeatmapMetadata + { + Author = { Username = "really long username that is autosized" }, + }, + OnlineID = 1, + } + })); + + AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); + AddAssert("author is set", () => difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Any()); + } + + [Test] + public void TestNullBeatmap() + { + AddStep("set beatmap", () => BeatmapInfo.Value = null); + } + } +} diff --git a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs new file mode 100644 index 000000000000..17782468415c --- /dev/null +++ b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs @@ -0,0 +1,98 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.SelectV2.Wedge +{ + public partial class DifficultyNameContent : CompositeDrawable + { + private OsuSpriteText difficultyName = null!; + private OsuSpriteText mappedByLabel = null!; + private LinkFlowContainer mapperName = null!; + + [Resolved] + private IBindable beatmapInfo { get; set; } = null!; + + public DifficultyNameContent() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + difficultyName = new TruncatingSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + }, + mappedByLabel = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + // TODO: better null display? beatmap carousel panels also just show this text currently. + Text = " mapped by ", + Font = OsuFont.GetFont(size: 14), + }, + mapperName = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14)) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmapInfo.BindValueChanged(b => + { + difficultyName.Text = b.NewValue?.DifficultyName ?? string.Empty; + updateMapper(); + }, true); + } + + private void updateMapper() + { + mapperName.Clear(); + + switch (beatmapInfo.Value) + { + case BeatmapInfo localBeatmap: + // TODO: should be the mapper of the guest difficulty, but that isn't stored correctly yet (see https://github.com/ppy/osu/issues/12965) + mapperName.AddUserLink(localBeatmap.Metadata.Author); + break; + } + } + + protected override void Update() + { + base.Update(); + + // truncate difficulty name when width exceeds bounds, prioritizing mapper name display + difficultyName.MaxWidth = Math.Max(DrawWidth - mappedByLabel.DrawWidth + - mapperName.DrawWidth, 0); + } + } +} From 2b41f71fd0d688627d8375e3f2ad28bf552a5ba2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 12 Aug 2024 23:31:00 -0700 Subject: [PATCH 264/579] Workaround single-frame layout issues with `{Link|Text|Fill}FlowContainer`s --- .../TestSceneDifficultyNameContent.cs | 4 +-- .../SelectV2/Wedge/DifficultyNameContent.cs | 35 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs index 884dae16176a..75bbf8f32a04 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -69,7 +69,7 @@ public override void SetUpSteps() public void TestLocalBeatmap() { AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); - AddAssert("author is not set", () => !difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Any()); + AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap { @@ -85,7 +85,7 @@ public void TestLocalBeatmap() })); AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); - AddAssert("author is set", () => difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Any()); + AddAssert("author is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); } [Test] diff --git a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs index 17782468415c..7df8b4a3cc36 100644 --- a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs +++ b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs @@ -10,6 +10,10 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; +using osu.Game.Online; +using osu.Game.Online.Chat; +using osu.Game.Overlays; namespace osu.Game.Screens.SelectV2.Wedge { @@ -17,11 +21,15 @@ public partial class DifficultyNameContent : CompositeDrawable { private OsuSpriteText difficultyName = null!; private OsuSpriteText mappedByLabel = null!; - private LinkFlowContainer mapperName = null!; + private OsuHoverContainer mapperLink = null!; + private OsuSpriteText mapperName = null!; [Resolved] private IBindable beatmapInfo { get; set; } = null!; + [Resolved] + private ILinkHandler? linkHandler { get; set; } + public DifficultyNameContent() { RelativeSizeAxes = Axes.X; @@ -52,11 +60,15 @@ private void load() Text = " mapped by ", Font = OsuFont.GetFont(size: 14), }, - mapperName = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14)) + mapperLink = new MapperLink { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, + Child = mapperName = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14), + } }, } }; @@ -75,13 +87,14 @@ protected override void LoadComplete() private void updateMapper() { - mapperName.Clear(); + mapperName.Text = string.Empty; switch (beatmapInfo.Value) { case BeatmapInfo localBeatmap: // TODO: should be the mapper of the guest difficulty, but that isn't stored correctly yet (see https://github.com/ppy/osu/issues/12965) - mapperName.AddUserLink(localBeatmap.Metadata.Author); + mapperName.Text = localBeatmap.Metadata.Author.Username; + mapperLink.Action = () => linkHandler?.HandleLink(new LinkDetails(LinkAction.OpenUserProfile, localBeatmap.Metadata.Author)); break; } } @@ -94,5 +107,19 @@ protected override void Update() difficultyName.MaxWidth = Math.Max(DrawWidth - mappedByLabel.DrawWidth - mapperName.DrawWidth, 0); } + + /// + /// This class is a workaround for the single-frame layout issues with `{Link|Text|Fill}FlowContainer`s. + /// See https://github.com/ppy/osu-framework/issues/3369. + /// + private partial class MapperLink : OsuHoverContainer + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider? overlayColourProvider, OsuColour colours) + { + TooltipText = ContextMenuStrings.ViewProfile; + IdleColour = overlayColourProvider?.Light2 ?? colours.Blue; + } + } } } From f8796e3192ebdc792a3a825399e913dbb6298ef2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 13 Aug 2024 22:15:10 -0700 Subject: [PATCH 265/579] Move resizing width and background logic to `SongSelectComponentsTestScene` --- .../SongSelectComponentsTestScene.cs | 45 ++++++++++++++++ .../TestSceneDifficultyNameContent.cs | 51 +------------------ 2 files changed, 47 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index ff81d72d1277..45483559922f 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -3,6 +3,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -11,6 +14,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { public abstract partial class SongSelectComponentsTestScene : OsuTestScene { + protected Container ComponentContainer = null!; + + private Container? resizeContainer; + private float relativeWidth; + [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); @@ -20,6 +28,18 @@ public abstract partial class SongSelectComponentsTestScene : OsuTestScene [Cached(typeof(IBindable))] protected readonly Bindable BeatmapInfo = new Bindable(); + [BackgroundDependencyLoader] + private void load() + { + AddSliderStep("change relative width", 0, 1f, 0.5f, v => + { + if (resizeContainer != null) + resizeContainer.Width = v; + + relativeWidth = v; + }); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -40,6 +60,31 @@ public virtual void SetUpSteps() SelectedMods.SetDefault(); BeatmapInfo.Value = null; }); + + AddStep("set content", () => + { + Child = resizeContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Width = relativeWidth, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background5, + }, + ComponentContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + } + } + }; + }); } } } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs index 75bbf8f32a04..b556268be056 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -3,10 +3,6 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -18,56 +14,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestScene { - private Container? content; private DifficultyNameContent? difficultyNameContent; - private float relativeWidth; - - [BackgroundDependencyLoader] - private void load() - { - AddSliderStep("change relative width", 0, 1f, 0.5f, v => - { - if (content != null) - content.Width = v; - - relativeWidth = v; - }); - } - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("set content", () => - { - Child = content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), - Width = relativeWidth, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), - Child = difficultyNameContent = new DifficultyNameContent(), - } - } - }; - }); - } [Test] public void TestLocalBeatmap() { + AddStep("set component", () => ComponentContainer.Child = difficultyNameContent = new DifficultyNameContent()); + AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); From c24f1444f9ee854a4c0d2cc5eb5ada9ab18fd743 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 13 Aug 2024 22:21:26 -0700 Subject: [PATCH 266/579] Directly resolve `IBindable` by making a local variant of `DifficultyNameContent` --- .../SongSelectComponentsTestScene.cs | 20 ------- .../TestSceneDifficultyNameContent.cs | 8 +-- .../SelectV2/Wedge/DifficultyNameContent.cs | 57 ++++--------------- .../Wedge/LocalDifficultyNameContent.cs | 34 +++++++++++ 4 files changed, 46 insertions(+), 73 deletions(-) create mode 100644 osu.Game/Screens/SelectV2/Wedge/LocalDifficultyNameContent.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index 45483559922f..d984a3a11a75 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.SongSelectV2 @@ -22,12 +20,6 @@ public abstract partial class SongSelectComponentsTestScene : OsuTestScene [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); - /// - /// The beatmap. Can be local/online depending on the context. - /// - [Cached(typeof(IBindable))] - protected readonly Bindable BeatmapInfo = new Bindable(); - [BackgroundDependencyLoader] private void load() { @@ -40,17 +32,6 @@ private void load() }); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // mimics song select's `WorkingBeatmap` binding - Beatmap.BindValueChanged(b => - { - BeatmapInfo.Value = b.NewValue.BeatmapInfo; - }); - } - [SetUpSteps] public virtual void SetUpSteps() { @@ -58,7 +39,6 @@ public virtual void SetUpSteps() { Beatmap.SetDefault(); SelectedMods.SetDefault(); - BeatmapInfo.Value = null; }); AddStep("set content", () => diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs index b556268be056..e32d6ddb80cf 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -19,7 +19,7 @@ public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestSc [Test] public void TestLocalBeatmap() { - AddStep("set component", () => ComponentContainer.Child = difficultyNameContent = new DifficultyNameContent()); + AddStep("set component", () => ComponentContainer.Child = difficultyNameContent = new LocalDifficultyNameContent()); AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); @@ -40,11 +40,5 @@ public void TestLocalBeatmap() AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); AddAssert("author is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); } - - [Test] - public void TestNullBeatmap() - { - AddStep("set beatmap", () => BeatmapInfo.Value = null); - } } } diff --git a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs index 7df8b4a3cc36..f49714bee835 100644 --- a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs +++ b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs @@ -3,34 +3,24 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; -using osu.Game.Online; -using osu.Game.Online.Chat; using osu.Game.Overlays; namespace osu.Game.Screens.SelectV2.Wedge { - public partial class DifficultyNameContent : CompositeDrawable + public abstract partial class DifficultyNameContent : CompositeDrawable { - private OsuSpriteText difficultyName = null!; + protected OsuSpriteText DifficultyName = null!; private OsuSpriteText mappedByLabel = null!; - private OsuHoverContainer mapperLink = null!; - private OsuSpriteText mapperName = null!; + protected OsuHoverContainer MapperLink = null!; + protected OsuSpriteText MapperName = null!; - [Resolved] - private IBindable beatmapInfo { get; set; } = null!; - - [Resolved] - private ILinkHandler? linkHandler { get; set; } - - public DifficultyNameContent() + protected DifficultyNameContent() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -46,7 +36,7 @@ private void load() Direction = FillDirection.Horizontal, Children = new Drawable[] { - difficultyName = new TruncatingSpriteText + DifficultyName = new TruncatingSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -60,12 +50,12 @@ private void load() Text = " mapped by ", Font = OsuFont.GetFont(size: 14), }, - mapperLink = new MapperLink + MapperLink = new MapperLinkContainer { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, - Child = mapperName = new OsuSpriteText + Child = MapperName = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14), } @@ -74,45 +64,20 @@ private void load() }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - beatmapInfo.BindValueChanged(b => - { - difficultyName.Text = b.NewValue?.DifficultyName ?? string.Empty; - updateMapper(); - }, true); - } - - private void updateMapper() - { - mapperName.Text = string.Empty; - - switch (beatmapInfo.Value) - { - case BeatmapInfo localBeatmap: - // TODO: should be the mapper of the guest difficulty, but that isn't stored correctly yet (see https://github.com/ppy/osu/issues/12965) - mapperName.Text = localBeatmap.Metadata.Author.Username; - mapperLink.Action = () => linkHandler?.HandleLink(new LinkDetails(LinkAction.OpenUserProfile, localBeatmap.Metadata.Author)); - break; - } - } - protected override void Update() { base.Update(); // truncate difficulty name when width exceeds bounds, prioritizing mapper name display - difficultyName.MaxWidth = Math.Max(DrawWidth - mappedByLabel.DrawWidth - - mapperName.DrawWidth, 0); + DifficultyName.MaxWidth = Math.Max(DrawWidth - mappedByLabel.DrawWidth + - MapperName.DrawWidth, 0); } /// /// This class is a workaround for the single-frame layout issues with `{Link|Text|Fill}FlowContainer`s. /// See https://github.com/ppy/osu-framework/issues/3369. /// - private partial class MapperLink : OsuHoverContainer + private partial class MapperLinkContainer : OsuHoverContainer { [BackgroundDependencyLoader] private void load(OverlayColourProvider? overlayColourProvider, OsuColour colours) diff --git a/osu.Game/Screens/SelectV2/Wedge/LocalDifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/LocalDifficultyNameContent.cs new file mode 100644 index 000000000000..66f8cb02b25d --- /dev/null +++ b/osu.Game/Screens/SelectV2/Wedge/LocalDifficultyNameContent.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Online; +using osu.Game.Online.Chat; + +namespace osu.Game.Screens.SelectV2.Wedge +{ + public partial class LocalDifficultyNameContent : DifficultyNameContent + { + [Resolved] + private IBindable beatmap { get; set; } = null!; + + [Resolved] + private ILinkHandler? linkHandler { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.BindValueChanged(b => + { + DifficultyName.Text = b.NewValue.BeatmapInfo.DifficultyName; + + // TODO: should be the mapper of the guest difficulty, but that isn't stored correctly yet (see https://github.com/ppy/osu/issues/12965) + MapperName.Text = b.NewValue.Metadata.Author.Username; + MapperLink.Action = () => linkHandler?.HandleLink(new LinkDetails(LinkAction.OpenUserProfile, b.NewValue.Metadata.Author)); + }, true); + } + } +} From 21745105445fc4d540b2c93f1fd70f8c223337ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 15:51:07 +0900 Subject: [PATCH 267/579] Move other V2 tests to new test namespace --- .../{SongSelect => SongSelectV2}/TestSceneBeatmapInfoWedgeV2.cs | 2 +- .../{SongSelect => SongSelectV2}/TestSceneLeaderboardScoreV2.cs | 2 +- .../{SongSelect => SongSelectV2}/TestSceneSongSelectV2.cs | 2 +- .../TestSceneSongSelectV2Navigation.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game.Tests/Visual/{SongSelect => SongSelectV2}/TestSceneBeatmapInfoWedgeV2.cs (99%) rename osu.Game.Tests/Visual/{SongSelect => SongSelectV2}/TestSceneLeaderboardScoreV2.cs (99%) rename osu.Game.Tests/Visual/{SongSelect => SongSelectV2}/TestSceneSongSelectV2.cs (99%) rename osu.Game.Tests/Visual/{SongSelect => SongSelectV2}/TestSceneSongSelectV2Navigation.cs (95%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs index 2a3269ea0adf..aad2bd6334ae 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs @@ -18,7 +18,7 @@ using osu.Game.Screens.Select; using osuTK; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.SongSelectV2 { [TestFixture] public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs index 33af4907a136..4a733b2cbef9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs @@ -24,7 +24,7 @@ using osu.Game.Users; using osuTK; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneLeaderboardScoreV2 : OsuTestScene { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs index 02f503d4330a..c93c41d558a0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs @@ -20,7 +20,7 @@ using osu.Game.Screens.SelectV2.Footer; using osuTK.Input; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneSongSelectV2 : ScreenTestScene { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs similarity index 95% rename from osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs index 0ca27c539af1..a72146352e7f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs @@ -7,7 +7,7 @@ using osu.Game.Screens.Menu; using osuTK.Input; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneSongSelectV2Navigation : OsuGameTestScene { From 11bd0c9a6141a61023cdb43e1f4d3755d55c5c21 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 14 Aug 2024 00:41:43 -0700 Subject: [PATCH 268/579] Inline single-frame layout issue comment instead --- osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs index f49714bee835..4a3dc34cf9c9 100644 --- a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs +++ b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs @@ -50,6 +50,8 @@ private void load() Text = " mapped by ", Font = OsuFont.GetFont(size: 14), }, + // This is not a `LinkFlowContainer` as there are single-frame layout issues when Update() + // is being used for layout, see https://github.com/ppy/osu-framework/issues/3369. MapperLink = new MapperLinkContainer { Anchor = Anchor.BottomLeft, @@ -73,10 +75,6 @@ protected override void Update() - MapperName.DrawWidth, 0); } - /// - /// This class is a workaround for the single-frame layout issues with `{Link|Text|Fill}FlowContainer`s. - /// See https://github.com/ppy/osu-framework/issues/3369. - /// private partial class MapperLinkContainer : OsuHoverContainer { [BackgroundDependencyLoader] From 6f2bc7e6f12350e66c4c5679d33d0e7db1490484 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 14 Aug 2024 00:44:03 -0700 Subject: [PATCH 269/579] Use `Content` override instead --- .../SongSelectComponentsTestScene.cs | 26 +++++++++---------- .../TestSceneDifficultyNameContent.cs | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index d984a3a11a75..1583d229c55b 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -12,14 +12,19 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { public abstract partial class SongSelectComponentsTestScene : OsuTestScene { - protected Container ComponentContainer = null!; + [Cached] + protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + protected override Container Content { get; } = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + }; private Container? resizeContainer; private float relativeWidth; - [Cached] - protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); - [BackgroundDependencyLoader] private void load() { @@ -41,9 +46,9 @@ public virtual void SetUpSteps() SelectedMods.SetDefault(); }); - AddStep("set content", () => + AddStep("setup content", () => { - Child = resizeContainer = new Container + base.Content.Add(resizeContainer = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -56,14 +61,9 @@ public virtual void SetUpSteps() RelativeSizeAxes = Axes.Both, Colour = ColourProvider.Background5, }, - ComponentContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), - } + Content } - }; + }); }); } } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs index e32d6ddb80cf..49e7e2bc1a8c 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -19,7 +19,7 @@ public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestSc [Test] public void TestLocalBeatmap() { - AddStep("set component", () => ComponentContainer.Child = difficultyNameContent = new LocalDifficultyNameContent()); + AddStep("set component", () => Child = difficultyNameContent = new LocalDifficultyNameContent()); AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); From 28ab65243d0159ba9ea7015ef58d7916c53c61e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 20:45:27 +0900 Subject: [PATCH 270/579] Remove daily challenge tooltip from main menu Now that we have a nice intro screen for the daily challenge, it's generally thought that we want to "spoil" the beatmap until the intro is shown. Also I was never a huge fan of having a tooltip on a main menu button.. just feels a bit odd. --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index e19ba6612c7c..e6593c9b0d5f 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -9,12 +9,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps.Drawables; -using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; @@ -30,7 +28,7 @@ namespace osu.Game.Screens.Menu { - public partial class DailyChallengeButton : MainMenuButton, IHasCustomTooltip + public partial class DailyChallengeButton : MainMenuButton { public Room? Room { get; private set; } @@ -201,36 +199,6 @@ protected override void UpdateState() base.UpdateState(); } - public ITooltip GetCustomTooltip() => new DailyChallengeTooltip(); - public APIBeatmapSet? TooltipContent { get; private set; } - - internal partial class DailyChallengeTooltip : CompositeDrawable, ITooltip - { - [Cached] - private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - private APIBeatmapSet? lastContent; - - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - } - - public void Move(Vector2 pos) => Position = pos; - - public void SetContent(APIBeatmapSet? content) - { - if (content == lastContent) - return; - - lastContent = content; - - ClearInternal(); - if (content != null) - AddInternal(new BeatmapCardNano(content)); - } - } } } From 1665d9a93e03177295036ac68c1ca551ad95df89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 21:01:35 +0900 Subject: [PATCH 271/579] Fix failing test setup --- .../SongSelectComponentsTestScene.cs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index 1583d229c55b..c7f159705172 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -28,6 +28,23 @@ public abstract partial class SongSelectComponentsTestScene : OsuTestScene [BackgroundDependencyLoader] private void load() { + base.Content.Child = resizeContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Width = relativeWidth, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background5, + }, + Content + } + }; + AddSliderStep("change relative width", 0, 1f, 0.5f, v => { if (resizeContainer != null) @@ -45,26 +62,6 @@ public virtual void SetUpSteps() Beatmap.SetDefault(); SelectedMods.SetDefault(); }); - - AddStep("setup content", () => - { - base.Content.Add(resizeContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), - Width = relativeWidth, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5, - }, - Content - } - }); - }); } } } From e603888130dce54965411cda873d05c2a4b63de3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 21:09:28 +0900 Subject: [PATCH 272/579] Update remaining tests to use new base class (and tidy up `V2` suffixes) --- .../SongSelectComponentsTestScene.cs | 2 +- ...edgeV2.cs => TestSceneBeatmapInfoWedge.cs} | 16 +++++++------- ...coreV2.cs => TestSceneLeaderboardScore.cs} | 21 ++----------------- ...SongSelectV2.cs => TestSceneSongSelect.cs} | 4 ++-- ...on.cs => TestSceneSongSelectNavigation.cs} | 2 +- 5 files changed, 14 insertions(+), 31 deletions(-) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneBeatmapInfoWedgeV2.cs => TestSceneBeatmapInfoWedge.cs} (97%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneLeaderboardScoreV2.cs => TestSceneLeaderboardScore.cs} (91%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneSongSelectV2.cs => TestSceneSongSelect.cs} (98%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneSongSelectV2Navigation.cs => TestSceneSongSelectNavigation.cs} (92%) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index c7f159705172..b7b0101a7c34 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -45,7 +45,7 @@ private void load() } }; - AddSliderStep("change relative width", 0, 1f, 0.5f, v => + AddSliderStep("change relative width", 0, 1f, 1f, v => { if (resizeContainer != null) resizeContainer.Width = v; diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs similarity index 97% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs index aad2bd6334ae..35bd4ee9583a 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs @@ -20,8 +20,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { - [TestFixture] - public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene + public partial class TestSceneBeatmapInfoWedge : SongSelectComponentsTestScene { private RulesetStore rulesets = null!; private TestBeatmapInfoWedgeV2 infoWedge = null!; @@ -33,6 +32,13 @@ private void load(RulesetStore rulesets) this.rulesets = rulesets; } + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("reset mods", () => SelectedMods.SetDefault()); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -107,12 +113,6 @@ private void testBeatmapLabels(Ruleset ruleset) AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); } - [SetUpSteps] - public void SetUpSteps() - { - AddStep("reset mods", () => SelectedMods.SetDefault()); - } - [Test] public void TestTruncation() { diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs similarity index 91% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs index 4a733b2cbef9..a7d0d70c03a9 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.Sprites; @@ -26,7 +25,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneLeaderboardScoreV2 : OsuTestScene + public partial class TestSceneLeaderboardScore : SongSelectComponentsTestScene { [Cached] private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine); @@ -36,19 +35,6 @@ public partial class TestSceneLeaderboardScoreV2 : OsuTestScene private FillFlowContainer? fillFlow; private OsuSpriteText? drawWidthText; - private float relativeWidth; - - [BackgroundDependencyLoader] - private void load() - { - // TODO: invalidation seems to be one-off when clicking slider to a certain value, so drag for now - // doesn't seem to happen in-game (when toggling window mode) - AddSliderStep("change relative width", 0, 1f, 0.6f, v => - { - relativeWidth = v; - if (fillFlow != null) fillFlow.Width = v; - }); - } [Test] public void TestSheared() @@ -59,7 +45,6 @@ public void TestSheared() { fillFlow = new FillFlowContainer { - Width = relativeWidth, Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, @@ -94,7 +79,6 @@ public void TestNonSheared() { fillFlow = new FillFlowContainer { - Width = relativeWidth, Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, @@ -118,8 +102,7 @@ public void TestNonSheared() }); } - [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { AddToggleStep("toggle scoring mode", v => config.SetValue(OsuSetting.ScoreDisplayMode, v ? ScoringMode.Classic : ScoringMode.Standardised)); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs similarity index 98% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index c93c41d558a0..d43026c9609d 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneSongSelectV2 : ScreenTestScene + public partial class TestSceneSongSelect : ScreenTestScene { [Cached] private readonly ScreenFooter screenScreenFooter; @@ -30,7 +30,7 @@ public partial class TestSceneSongSelectV2 : ScreenTestScene [Cached] private readonly OsuLogo logo; - public TestSceneSongSelectV2() + public TestSceneSongSelect() { Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectNavigation.cs similarity index 92% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectNavigation.cs index a72146352e7f..5173cb56730a 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectNavigation.cs @@ -9,7 +9,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneSongSelectV2Navigation : OsuGameTestScene + public partial class TestSceneSongSelectNavigation : OsuGameTestScene { public override void SetUpSteps() { From 054366b25dc83a3c7ed4598bb7b341b3ec7c568b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2024 13:07:42 +0900 Subject: [PATCH 273/579] Use zero baseline for legacy sprite text display --- osu.Game/Skinning/LegacySpriteText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index fdd8716d5ae0..1028b5bb9d11 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -96,7 +96,7 @@ public LegacyGlyphStore(string fontName, ISkin skin, Vector2? maxSize) if (maxSize != null) texture = texture.WithMaximumSize(maxSize.Value); - glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 1f / texture.ScaleAdjust); + glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, 0, null), texture, 1f / texture.ScaleAdjust); } cache[character] = glyph; From ff1ab2bb0ef28335a834a1175a6dbc55b9e3f8b6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 14:59:40 +0900 Subject: [PATCH 274/579] Remove position-flipping logic from mania combo counters for now We need a general method to do this amicably, such as an HUD target that flips the position of its children when the direction is flipped. Something to consider later. --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 34 ------------------- .../Legacy/LegacyManiaComboCounter.cs | 34 ------------------- 2 files changed, 68 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 43d4e89cdbfd..04c08cc50966 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -1,46 +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 osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; namespace osu.Game.Rulesets.Mania.Skinning.Argon { public partial class ArgonManiaComboCounter : ArgonComboCounter { - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } = null!; - private IBindable direction = null!; - - protected override void LoadComplete() - { - base.LoadComplete(); - - direction = scrollingInfo.Direction.GetBoundCopy(); - direction.BindValueChanged(_ => updateAnchor()); - - // two schedules are required so that updateAnchor is executed in the next frame, - // which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer. - Schedule(() => Schedule(updateAnchor)); - } - - private void updateAnchor() - { - // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction - if (!Anchor.HasFlag(Anchor.y1)) - { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; - } - - // since we flip the vertical anchor when changing scroll direction, - // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. - if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) - Y = -Y; - } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 5832210836df..000e96540ac1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -24,38 +22,6 @@ private void load(ISkinSource skin) PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; } - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } = null!; - - private IBindable direction = null!; - - protected override void LoadComplete() - { - base.LoadComplete(); - - direction = scrollingInfo.Direction.GetBoundCopy(); - direction.BindValueChanged(_ => updateAnchor()); - - // two schedules are required so that updateAnchor is executed in the next frame, - // which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer. - Schedule(() => Schedule(updateAnchor)); - } - - private void updateAnchor() - { - // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction - if (!Anchor.HasFlag(Anchor.y1)) - { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; - } - - // since we flip the vertical anchor when changing scroll direction, - // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. - if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) - Y = -Y; - } - protected override void OnCountIncrement() { base.OnCountIncrement(); From 3a4546d62df3ceb50ddc7dac61e3989488b1a239 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 15:02:28 +0900 Subject: [PATCH 275/579] Remove `x` symbol from argon mania combo counter --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 04c08cc50966..2f93a1fb90ac 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { public partial class ArgonManiaComboCounter : ArgonComboCounter { - + protected override bool DisplayXSymbol => false; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 3f74a8d4e8b5..e82e8f4b6f9e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -23,6 +23,8 @@ public partial class ArgonComboCounter : ComboCounter protected override double RollingDuration => 250; + protected virtual bool DisplayXSymbol => true; + [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { @@ -76,15 +78,15 @@ private void updateWireframe() private int getDigitsRequiredForDisplayCount() { - // one for the single presumed starting digit, one for the "x" at the end. - int digitsRequired = 2; + // one for the single presumed starting digit, one for the "x" at the end (unless disabled). + int digitsRequired = DisplayXSymbol ? 2 : 1; long c = DisplayedCount; while ((c /= 10) > 0) digitsRequired++; return digitsRequired; } - protected override LocalisableString FormatCount(int count) => $@"{count}x"; + protected override LocalisableString FormatCount(int count) => DisplayXSymbol ? $@"{count}x" : count.ToString(); protected override IHasText CreateText() => Text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) { From 66adddbfb8020c019bbdf672ade3f00b4aead7ef Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 15:48:17 +0900 Subject: [PATCH 276/579] Actually bring back position-flipping logic and disable "closest" anchor --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 40 +++++++++++++++++++ .../Legacy/LegacyManiaComboCounter.cs | 34 ++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 2f93a1fb90ac..8d51b593243d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -1,6 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; namespace osu.Game.Rulesets.Mania.Skinning.Argon @@ -8,5 +12,41 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon public partial class ArgonManiaComboCounter : ArgonComboCounter { protected override bool DisplayXSymbol => false; + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + + private IBindable direction = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + // the logic of flipping the position of the combo counter w.r.t. the direction does not work with "Closest" anchor, + // because it always forces the anchor to be top or bottom based on scrolling direction. + UsesFixedAnchor = true; + + direction = scrollingInfo.Direction.GetBoundCopy(); + direction.BindValueChanged(_ => updateAnchor()); + + // two schedules are required so that updateAnchor is executed in the next frame, + // which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer. + Schedule(() => Schedule(updateAnchor)); + } + + private void updateAnchor() + { + // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction + if (!Anchor.HasFlag(Anchor.y1)) + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + } + + // since we flip the vertical anchor when changing scroll direction, + // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. + if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) + Y = -Y; + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 000e96540ac1..5832210836df 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -22,6 +24,38 @@ private void load(ISkinSource skin) PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; } + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + + private IBindable direction = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + direction = scrollingInfo.Direction.GetBoundCopy(); + direction.BindValueChanged(_ => updateAnchor()); + + // two schedules are required so that updateAnchor is executed in the next frame, + // which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer. + Schedule(() => Schedule(updateAnchor)); + } + + private void updateAnchor() + { + // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction + if (!Anchor.HasFlag(Anchor.y1)) + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + } + + // since we flip the vertical anchor when changing scroll direction, + // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. + if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) + Y = -Y; + } + protected override void OnCountIncrement() { base.OnCountIncrement(); From 26da2c06372ca53e1772a59db6d7422946a4ee39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2024 16:16:48 +0900 Subject: [PATCH 277/579] Update `MultiplayerClient` test output with new knowledge --- .../Online/Multiplayer/MultiplayerClient.cs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index d2c69c2ceb57..07e779c2babf 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -5,14 +5,15 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Game.Database; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer.Countdown; @@ -22,7 +23,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Utils; -using osu.Game.Localisation; namespace osu.Game.Online.Multiplayer { @@ -777,26 +777,22 @@ public Task PlaylistItemChanged(MultiplayerPlaylistItem item) Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item; int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); + APIRoom.Playlist.RemoveAt(existingIndex); APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item)); } catch (Exception ex) { // Temporary code to attempt to figure out long-term failing tests. - bool success = true; - int indexOf = -1234; + StringBuilder exceptionText = new StringBuilder(); - try - { - indexOf = APIRoom!.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); - Room.Playlist[indexOf] = item; - } - catch - { - success = false; - } + exceptionText.AppendLine("MultiplayerClient test failure investigation"); + exceptionText.AppendLine($"Exception : {exceptionText}"); + exceptionText.AppendLine($"Lookup : {item.ID}"); + exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}"); + exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}"); - throw new AggregateException($"Index: {indexOf} Length: {Room.Playlist.Count} Retry success: {success} Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex); + throw new AggregateException(exceptionText.ToString()); } ItemChanged?.Invoke(item); From 4b279ecaa8b1209a65f3a44667aac9cbb9dfdb93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2024 16:44:15 +0900 Subject: [PATCH 278/579] Fix mistake --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 07e779c2babf..4aa0d9209801 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -787,7 +787,7 @@ public Task PlaylistItemChanged(MultiplayerPlaylistItem item) StringBuilder exceptionText = new StringBuilder(); exceptionText.AppendLine("MultiplayerClient test failure investigation"); - exceptionText.AppendLine($"Exception : {exceptionText}"); + exceptionText.AppendLine($"Exception : {ex.ToString()}"); exceptionText.AppendLine($"Lookup : {item.ID}"); exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}"); exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}"); From 49c71f78631879a30177ef59797e379d2d9ad251 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 16:16:52 +0900 Subject: [PATCH 279/579] Fix beatmap skin always overriding ruleset HUD components --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 4 ++++ .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 44fc3ecc0747..394fc5080d8b 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -43,6 +43,10 @@ public CatchLegacySkinTransformer(ISkin skin) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; + // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). + if (!IsProvidingLegacyResources) + return null; + // Our own ruleset components default. // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 9a8eaa7d7d31..a0265dd6ee7d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -53,6 +53,10 @@ public OsuLegacySkinTransformer(ISkin skin) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; + // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). + if (!IsProvidingLegacyResources) + return null; + // Our own ruleset components default. switch (containerLookup.Target) { From a421231aad9894cb29d07646f465443ac607496c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 16:55:03 +0900 Subject: [PATCH 280/579] Fix beatmap skin on mania breaking HUD apart --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 6ac6f6ed1898..1e06eb48179c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -92,6 +92,10 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; + // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). + if (!IsProvidingLegacyResources) + return null; + return new DefaultSkinComponentsContainer(container => { var combo = container.ChildrenOfType().FirstOrDefault(); From 358572ebb32b83c72129d88aa00046b4f82c60bc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 16:57:29 +0900 Subject: [PATCH 281/579] Update code order to match everything else --- .../Argon/ManiaArgonSkinTransformer.cs | 37 ++++++++++--------- .../Legacy/ManiaLegacySkinTransformer.cs | 35 ++++++++++-------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 224db77f5901..dbd690f89096 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -29,9 +29,6 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) switch (lookup) { case SkinComponentsContainerLookup containerLookup: - if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) - return base.GetDrawableComponent(lookup); - // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -40,21 +37,27 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; - return new DefaultSkinComponentsContainer(container => - { - var combo = container.ChildrenOfType().FirstOrDefault(); - - if (combo != null) - { - combo.ShowLabel.Value = false; - combo.Anchor = Anchor.TopCentre; - combo.Origin = Anchor.Centre; - combo.Y = 200; - } - }) + switch (containerLookup.Target) { - new ArgonManiaComboCounter(), - }; + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); + + if (combo != null) + { + combo.ShowLabel.Value = false; + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = 200; + } + }) + { + new ArgonManiaComboCounter(), + }; + } + + return null; case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 1e06eb48179c..c25b77610aba 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -81,9 +81,6 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) switch (lookup) { case SkinComponentsContainerLookup containerLookup: - if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) - return base.GetDrawableComponent(lookup); - // Modifications for global components. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -96,20 +93,26 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) if (!IsProvidingLegacyResources) return null; - return new DefaultSkinComponentsContainer(container => - { - var combo = container.ChildrenOfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.TopCentre; - combo.Origin = Anchor.Centre; - combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; - } - }) + switch (containerLookup.Target) { - new LegacyManiaComboCounter(), - }; + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; + } + }) + { + new LegacyManiaComboCounter(), + }; + } + + return null; case GameplaySkinComponentLookup resultComponent: return getResult(resultComponent.Component); From 74272378735ddd9f88b0df0f6c53ed7edeb38170 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 17:03:08 +0900 Subject: [PATCH 282/579] Try make code look better --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 8d51b593243d..5b23cea49623 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,10 +44,9 @@ private void updateAnchor() Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; } - // since we flip the vertical anchor when changing scroll direction, - // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. - if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) - Y = -Y; + // change the sign of the Y coordinate in line with the scrolling direction. + // i.e. if the user changes direction from down to up, the anchor is changed from top to bottom, and the Y is flipped from positive to negative here. + Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1); } } } From b5f615882f1645d8a2cdd43cd2c8eb606068fde5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2024 17:25:30 +0900 Subject: [PATCH 283/579] Ensure the "Change Difficulty" menu uses up-to-date difficulty names Closes https://github.com/ppy/osu/issues/29391. --- osu.Game/Screens/Edit/Editor.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 71d4693ac603..167ac9287486 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1286,10 +1286,23 @@ private EditorMenuItem createDifficultySwitchMenu() foreach (var beatmap in rulesetBeatmaps) { bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmap); - difficultyItems.Add(new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty)); + var difficultyMenuItem = new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty); + difficultyItems.Add(difficultyMenuItem); } } + // Ensure difficulty names are updated when modified in the editor. + // Maybe we could trigger less often but this seems to work well enough. + editorBeatmap.SaveStateTriggered += () => + { + foreach (var beatmapInfo in Beatmap.Value.BeatmapSetInfo.Beatmaps) + { + var menuItem = difficultyItems.OfType().FirstOrDefault(i => i.BeatmapInfo.Equals(beatmapInfo)); + if (menuItem != null) + menuItem.Text.Value = string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? "(unnamed)" : beatmapInfo.DifficultyName; + } + }; + return new EditorMenuItem(EditorStrings.ChangeDifficulty) { Items = difficultyItems }; } From c3600467bf38eb281d41cc35f3ba469c9251a38f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 15 Aug 2024 11:49:15 -0700 Subject: [PATCH 284/579] Make collection button test less broken --- osu.Game/Screens/Ranking/CollectionPopover.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/CollectionPopover.cs b/osu.Game/Screens/Ranking/CollectionPopover.cs index e285c8005644..6617ac334f7a 100644 --- a/osu.Game/Screens/Ranking/CollectionPopover.cs +++ b/osu.Game/Screens/Ranking/CollectionPopover.cs @@ -58,8 +58,7 @@ private OsuMenuItem[] items .AsEnumerable() .Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmapInfo)).Cast().ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, () => manageCollectionsDialog?.Show())); return collectionItems.ToArray(); } From f717938a288974cef9f4fb4c94c18024158c7549 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 15 Aug 2024 22:49:05 +0200 Subject: [PATCH 285/579] Fix grid snap slider placement double-click does not make new segment if anchor not hovered --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 91cd270af6cf..013f790f65dc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -359,8 +359,7 @@ private void updateCursor() } // Update the cursor position. - var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All); - cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position; + cursor.Position = getCursorPosition(); } else if (cursor != null) { @@ -374,6 +373,12 @@ private void updateCursor() } } + private Vector2 getCursorPosition() + { + var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All); + return ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position; + } + /// /// Whether a new control point can be placed at the current mouse position. /// @@ -386,7 +391,9 @@ private bool canPlaceNewControlPoint([CanBeNull] out PathControlPoint lastPoint) var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last); lastPoint = last; - return lastPiece.IsHovered != true; + // We may only place a new control point if the cursor is not overlapping with the last control point. + // If snapping is enabled, the cursor may not hover the last piece while still placing the control point at the same position. + return !lastPiece.IsHovered && (last is null || Vector2.DistanceSquared(last.Position, getCursorPosition()) > 1f); } private void placeNewControlPoint() From 29fda745a42a12b9e53f7e6b417c9219e0701d58 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 15 Aug 2024 22:59:26 +0200 Subject: [PATCH 286/579] add failure test --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index bc1e4f986409..aa6a6f08d878 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -299,6 +299,14 @@ public void TestManualPathTypeControlViaKeyboard() }); assertControlPointTypeDuringPlacement(0, PathType.BSpline(4)); + AddStep("press alt-2", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.Number2); + InputManager.ReleaseKey(Key.AltLeft); + }); + assertControlPointTypeDuringPlacement(0, PathType.BEZIER); + AddStep("start new segment via S", () => InputManager.Key(Key.S)); assertControlPointTypeDuringPlacement(2, PathType.LINEAR); @@ -309,7 +317,7 @@ public void TestManualPathTypeControlViaKeyboard() addClickStep(MouseButton.Right); assertPlaced(true); - assertFinalControlPointType(0, PathType.BSpline(4)); + assertFinalControlPointType(0, PathType.BEZIER); assertFinalControlPointType(2, PathType.PERFECT_CURVE); } From 00e210147a457849a799296493e965efb029ad38 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 15 Aug 2024 23:11:07 +0200 Subject: [PATCH 287/579] Fix inputs being eaten by PathControlPointVisualizer when no control points are selected --- .../Sliders/Components/PathControlPointVisualiser.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 6251d17d856b..df369dcef5ee 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -309,8 +309,13 @@ protected override bool OnKeyDown(KeyDownEvent e) if (!e.AltPressed) return false; + // If no pieces are selected, we can't change the path type. + if (Pieces.All(p => !p.IsSelected.Value)) + return false; + var type = path_types[e.Key - Key.Number1]; + // The first control point can never be inherit type if (Pieces[0].IsSelected.Value && type == null) return false; From ac064e814f4c6f8d465afcc41237211141b1193f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 00:15:40 +0200 Subject: [PATCH 288/579] Add BinarySearchUtils --- .../ControlPoints/ControlPointInfo.cs | 28 +----- osu.Game/Utils/BinarySearchUtils.cs | 99 +++++++++++++++++++ 2 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 osu.Game/Utils/BinarySearchUtils.cs diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index f8e72a1e341b..4fc77084d64b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -230,32 +230,12 @@ public static T BinarySearch(IReadOnlyList list, double time) { ArgumentNullException.ThrowIfNull(list); - if (list.Count == 0) - return null; + int index = BinarySearchUtils.BinarySearch(list, time, c => c.Time, EqualitySelection.Rightmost); - if (time < list[0].Time) - return null; + if (index < 0) + index = ~index - 1; - if (time >= list[^1].Time) - return list[^1]; - - int l = 0; - int r = list.Count - 2; - - while (l <= r) - { - int pivot = l + ((r - l) >> 1); - - if (list[pivot].Time < time) - l = pivot + 1; - else if (list[pivot].Time > time) - r = pivot - 1; - else - return list[pivot]; - } - - // l will be the first control point with Time > time, but we want the one before it - return list[l - 1]; + return index >= 0 ? list[index] : null; } /// diff --git a/osu.Game/Utils/BinarySearchUtils.cs b/osu.Game/Utils/BinarySearchUtils.cs new file mode 100644 index 000000000000..de5fc101d525 --- /dev/null +++ b/osu.Game/Utils/BinarySearchUtils.cs @@ -0,0 +1,99 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; + +namespace osu.Game.Utils +{ + public class BinarySearchUtils + { + /// + /// Finds the index of the item in the sorted list which has its property equal to the search term. + /// If no exact match is found, the complement of the index of the first item greater than the search term will be returned. + /// + /// The type of the items in the list to search. + /// The type of the property to perform the search on. + /// The list of items to search. + /// The query to find. + /// Function that maps an item in the list to its index property. + /// Determines which index to return if there are multiple exact matches. + /// The index of the found item. Will return the complement of the index of the first item greater than the search query if no exact match is found. + public static int BinarySearch(IReadOnlyList list, T2 searchTerm, Func termFunc, EqualitySelection equalitySelection = EqualitySelection.FirstFound) + { + int n = list.Count; + + if (n == 0) + return -1; + + var comparer = Comparer.Default; + + if (comparer.Compare(searchTerm, termFunc(list[0])) == -1) + return -1; + + if (comparer.Compare(searchTerm, termFunc(list[^1])) == 1) + return ~n; + + int min = 0; + int max = n - 1; + bool equalityFound = false; + + while (min <= max) + { + int mid = min + (max - min) / 2; + T2 midTerm = termFunc(list[mid]); + + switch (comparer.Compare(midTerm, searchTerm)) + { + case 0: + equalityFound = true; + + switch (equalitySelection) + { + case EqualitySelection.Leftmost: + max = mid - 1; + break; + + case EqualitySelection.Rightmost: + min = mid + 1; + break; + + default: + case EqualitySelection.FirstFound: + return mid; + } + + break; + + case 1: + max = mid - 1; + break; + + case -1: + min = mid + 1; + break; + } + } + + if (!equalityFound) return ~min; + + switch (equalitySelection) + { + case EqualitySelection.Leftmost: + return min; + + case EqualitySelection.Rightmost: + return min - 1; + } + + return ~min; + } + } + + public enum EqualitySelection + { + FirstFound, + Leftmost, + Rightmost + } +} From 2e11172e8e7f2cd0b2d2686920259c89edcb78b6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 01:01:24 +0200 Subject: [PATCH 289/579] Take into account next timing point when snapping time --- .../ControlPoints/ControlPointInfo.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 4fc77084d64b..026d44faa1c1 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -74,6 +74,19 @@ public class ControlPointInfo : IDeepCloneable [NotNull] public TimingControlPoint TimingPointAt(double time) => BinarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT); + /// + /// Finds the first timing point that is active strictly after , or null if no such point exists. + /// + /// The time after which to find the timing control point. + /// The timing control point. + [CanBeNull] + public TimingControlPoint TimingPointAfter(double time) + { + int index = BinarySearchUtils.BinarySearch(TimingPoints, time, c => c.Time, EqualitySelection.Rightmost); + index = index < 0 ? ~index : index + 1; + return index < TimingPoints.Count ? TimingPoints[index] : null; + } + /// /// Finds the maximum BPM represented by any timing control point. /// @@ -156,7 +169,14 @@ public void RemoveGroup(ControlPointGroup group) public double GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = TimingPointAt(referenceTime ?? time); - return getClosestSnappedTime(timingPoint, time, beatDivisor); + double snappedTime = getClosestSnappedTime(timingPoint, time, beatDivisor); + + if (referenceTime.HasValue) + return snappedTime; + + // If there is a timing point right after the given time, we should check if it is closer than the snapped time and snap to it. + var timingPointAfter = TimingPointAfter(time); + return timingPointAfter is null || Math.Abs(time - snappedTime) < Math.Abs(time - timingPointAfter.Time) ? snappedTime : timingPointAfter.Time; } /// From 3a84409546386f552c2f4d31773dfef0eb400b51 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 01:36:51 +0200 Subject: [PATCH 290/579] Use TimingPointAfter for seeking check --- osu.Game/Screens/Edit/EditorClock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 773abaa737ba..5b9c662c95a5 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -132,7 +132,7 @@ private void seek(int direction, bool snapped, double amount = 1) seekTime = timingPoint.Time + closestBeat * seekAmount; // limit forward seeking to only up to the next timing point's start time. - var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPointAfter(timingPoint.Time); if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; From 3565a10ea2c00d7a617be229faf723156a715f1c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 01:45:28 +0200 Subject: [PATCH 291/579] fix confusing return statement at the end --- osu.Game/Utils/BinarySearchUtils.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Utils/BinarySearchUtils.cs b/osu.Game/Utils/BinarySearchUtils.cs index de5fc101d525..08ce4e363d4c 100644 --- a/osu.Game/Utils/BinarySearchUtils.cs +++ b/osu.Game/Utils/BinarySearchUtils.cs @@ -82,11 +82,10 @@ public static int BinarySearch(IReadOnlyList list, T2 searchTerm, Func case EqualitySelection.Leftmost: return min; + default: case EqualitySelection.Rightmost: return min - 1; } - - return ~min; } } From fda17a5a72f327139cf982ebb68fbb25add6b5b4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 13 Aug 2024 23:52:52 -0700 Subject: [PATCH 292/579] Expose `BeatmapCardNormal` height const --- .../Beatmaps/Drawables/Cards/BeatmapCardNormal.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs index c6ba4f234aaf..46ab7ec5f628 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs @@ -23,7 +23,7 @@ public partial class BeatmapCardNormal : BeatmapCard protected override Drawable IdleContent => idleBottomContent; protected override Drawable DownloadInProgressContent => downloadProgressBar; - private const float height = 100; + public const float HEIGHT = 100; [Cached] private readonly BeatmapCardContent content; @@ -42,14 +42,14 @@ public partial class BeatmapCardNormal : BeatmapCard public BeatmapCardNormal(APIBeatmapSet beatmapSet, bool allowExpansion = true) : base(beatmapSet, allowExpansion) { - content = new BeatmapCardContent(height); + content = new BeatmapCardContent(HEIGHT); } [BackgroundDependencyLoader] private void load() { Width = WIDTH; - Height = height; + Height = HEIGHT; FillFlowContainer leftIconArea = null!; FillFlowContainer titleBadgeArea = null!; @@ -65,7 +65,7 @@ private void load() thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet) { Name = @"Left (icon) area", - Size = new Vector2(height), + Size = new Vector2(HEIGHT), Padding = new MarginPadding { Right = CORNER_RADIUS }, Child = leftIconArea = new FillFlowContainer { @@ -77,8 +77,8 @@ private void load() }, buttonContainer = new CollapsibleButtonContainer(BeatmapSet) { - X = height - CORNER_RADIUS, - Width = WIDTH - height + CORNER_RADIUS, + X = HEIGHT - CORNER_RADIUS, + Width = WIDTH - HEIGHT + CORNER_RADIUS, FavouriteState = { BindTarget = FavouriteState }, ButtonsCollapsedWidth = CORNER_RADIUS, ButtonsExpandedWidth = 30, From e2bf02cf948edc82d7cbe1049b0fe9638fc656bc Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 10 Aug 2024 00:49:51 -0700 Subject: [PATCH 293/579] Fix preview play button having incorrect click area --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs | 5 ++--- osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 976f797760fb..1f6f63861830 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -90,10 +90,9 @@ protected override void LoadComplete() protected override void Update() { base.Update(); - progress.Progress = playButton.Progress.Value; - playButton.Scale = new Vector2(DrawWidth / 100); - progress.Size = new Vector2(50 * DrawWidth / 100); + progress.Progress = playButton.Progress.Value; + progress.Size = new Vector2(50 * playButton.DrawWidth / (BeatmapCardNormal.HEIGHT - BeatmapCard.CORNER_RADIUS)); } private void updateState() diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index f808fd21b781..f6caf4815d67 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -79,6 +79,8 @@ protected override void Update() { base.Update(); + icon.Scale = new Vector2(DrawWidth / (BeatmapCardNormal.HEIGHT - BeatmapCard.CORNER_RADIUS)); + if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded) progress.Value = previewTrack.CurrentTime / previewTrack.Length; else From 68bad9a277b33b9ae35c2f4ea1fd7d3a128832b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2024 17:39:45 +0900 Subject: [PATCH 294/579] Attempt file operations more than once in another test instance See https://github.com/ppy/osu/pull/29433/checks?check_run_id=28833985792. --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 2b2358198443..db9ecd90b9fe 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -276,8 +276,11 @@ public void TestAttemptPlayBeatmapMissingFails() AddStep("delete beatmap files", () => { - foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) - Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath())); + FileUtils.AttemptOperation(() => + { + foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) + Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath())); + }); }); AddStep("invalidate cache", () => From e0da4763462a5743ca0ba77f57e0c4b79b81aa47 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 16 Aug 2024 18:12:46 +0900 Subject: [PATCH 295/579] Add tests for util function --- osu.Game.Tests/Utils/BinarySearchUtilsTest.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 osu.Game.Tests/Utils/BinarySearchUtilsTest.cs diff --git a/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs b/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs new file mode 100644 index 000000000000..bc125ec76ccb --- /dev/null +++ b/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Game.Utils; + +namespace osu.Game.Tests.Utils +{ + [TestFixture] + public class BinarySearchUtilsTest + { + [Test] + public void TestEmptyList() + { + Assert.That(BinarySearchUtils.BinarySearch(Array.Empty(), 0, x => x), Is.EqualTo(-1)); + Assert.That(BinarySearchUtils.BinarySearch(Array.Empty(), 0, x => x, EqualitySelection.Leftmost), Is.EqualTo(-1)); + Assert.That(BinarySearchUtils.BinarySearch(Array.Empty(), 0, x => x, EqualitySelection.Rightmost), Is.EqualTo(-1)); + } + + [TestCase(new[] { 1 }, 0, -1)] + [TestCase(new[] { 1 }, 1, 0)] + [TestCase(new[] { 1 }, 2, -2)] + [TestCase(new[] { 1, 3 }, 0, -1)] + [TestCase(new[] { 1, 3 }, 1, 0)] + [TestCase(new[] { 1, 3 }, 2, -2)] + [TestCase(new[] { 1, 3 }, 3, 1)] + [TestCase(new[] { 1, 3 }, 4, -3)] + public void TestUniqueScenarios(int[] values, int search, int expectedIndex) + { + Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.FirstFound), Is.EqualTo(expectedIndex)); + Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Leftmost), Is.EqualTo(expectedIndex)); + Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Rightmost), Is.EqualTo(expectedIndex)); + } + + [TestCase(new[] { 1, 2, 2 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 2 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 2)] + [TestCase(new[] { 1, 2, 2, 3 }, 2, 1)] + public void TestFirstFoundDuplicateScenarios(int[] values, int search, int expectedIndex) + { + Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x), Is.EqualTo(expectedIndex)); + } + + [TestCase(new[] { 1, 2, 2 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 2 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 3 }, 2, 1)] + public void TestLeftMostDuplicateScenarios(int[] values, int search, int expectedIndex) + { + Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Leftmost), Is.EqualTo(expectedIndex)); + } + + [TestCase(new[] { 1, 2, 2 }, 2, 2)] + [TestCase(new[] { 1, 2, 2, 2 }, 2, 3)] + [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 3)] + [TestCase(new[] { 1, 2, 2, 3 }, 2, 2)] + public void TestRightMostDuplicateScenarios(int[] values, int search, int expectedIndex) + { + Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Rightmost), Is.EqualTo(expectedIndex)); + } + } +} From 7a47597234a0d45f80af6e80b0a2fd23afb8f00c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 16 Aug 2024 18:21:06 +0900 Subject: [PATCH 296/579] Add one more case --- osu.Game.Tests/Utils/BinarySearchUtilsTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs b/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs index bc125ec76ccb..cbf6cdf32ae6 100644 --- a/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs +++ b/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs @@ -33,6 +33,7 @@ public void TestUniqueScenarios(int[] values, int search, int expectedIndex) Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Rightmost), Is.EqualTo(expectedIndex)); } + [TestCase(new[] { 1, 1 }, 1, 0)] [TestCase(new[] { 1, 2, 2 }, 2, 1)] [TestCase(new[] { 1, 2, 2, 2 }, 2, 1)] [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 2)] @@ -42,6 +43,7 @@ public void TestFirstFoundDuplicateScenarios(int[] values, int search, int expec Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x), Is.EqualTo(expectedIndex)); } + [TestCase(new[] { 1, 1 }, 1, 0)] [TestCase(new[] { 1, 2, 2 }, 2, 1)] [TestCase(new[] { 1, 2, 2, 2 }, 2, 1)] [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 1)] @@ -51,6 +53,7 @@ public void TestLeftMostDuplicateScenarios(int[] values, int search, int expecte Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Leftmost), Is.EqualTo(expectedIndex)); } + [TestCase(new[] { 1, 1 }, 1, 1)] [TestCase(new[] { 1, 2, 2 }, 2, 2)] [TestCase(new[] { 1, 2, 2, 2 }, 2, 3)] [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 3)] From e5fab9cfbe2cf20225dbf9cfe94f5a23d8cff711 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 11:55:07 +0200 Subject: [PATCH 297/579] Remove select action to end placement --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 60b979da5998..a50a7f416991 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -125,10 +125,6 @@ public bool OnPressed(KeyBindingPressEvent e) switch (e.Action) { - case GlobalAction.Select: - EndPlacement(true); - return true; - case GlobalAction.Back: EndPlacement(false); return true; From 5624c1d304a8cf40428d88e4e36b5262a1274604 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 13:22:09 +0200 Subject: [PATCH 298/579] Make break periods in bottom timeline transparent --- .../Edit/Components/Timelines/Summary/Parts/BreakPart.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 17e0d47676c8..3cff976f72e8 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -70,7 +70,8 @@ private void load(OsuColour colours) RelativeSizeAxes = Axes.Both; InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; - Colour = colours.Gray6; + Colour = colours.Gray7; + Alpha = 0.8f; } public LocalisableString TooltipText => $"{breakPeriod.StartTime.ToEditorFormattedString()} - {breakPeriod.EndTime.ToEditorFormattedString()} break time"; From 4cc38cea63a97611d6f5fbc902700c2e374d4dae Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 14:24:03 +0200 Subject: [PATCH 299/579] fix last anchor converting to implicit segment --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 8a8964ccd442..b0173b3ae3e1 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -450,7 +450,7 @@ private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position) // Explicit segments have a new format in which the type is injected into the middle of the control point string. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments - bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECT_CURVE; + bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECT_CURVE || i == pathData.Path.ControlPoints.Count - 1; // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. From a2e26ba9ffb93308e73263a7243833d429b873cb Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 14:24:55 +0200 Subject: [PATCH 300/579] Fix perfect curve anchors losing type between reloads --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 8e6ffa20cc1b..d4e30538561c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -347,7 +347,7 @@ private IEnumerable> convertPoints(PathType type, vertices[i] = new PathControlPoint { Position = points[i] }; // Edge-case rules (to match stable). - if (type == PathType.PERFECT_CURVE) + if (type == PathType.PERFECT_CURVE && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) { int endPointLength = endPoint == null ? 0 : 1; From b253d8ecbf3c6399e1fd84eb8738d03608db6ba2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 14:43:09 +0200 Subject: [PATCH 301/579] Hide scroll speed in bottom timeline --- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index 17fedb933a89..e3f90558c504 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -9,8 +9,10 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Graphics; +using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { @@ -79,7 +81,10 @@ private void refreshDisplay() { ClearInternal(); - AddInternal(new ControlPointVisualisation(effect)); + var drawableRuleset = beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateDrawableRulesetWith(beatmap.PlayableBeatmap); + + if (drawableRuleset is IDrawableScrollingRuleset scrollingRuleset && scrollingRuleset.VisualisationMethod != ScrollVisualisationMethod.Constant) + AddInternal(new ControlPointVisualisation(effect)); if (!kiai.Value) return; From 621c4d65a3d721ebb0547de891c2048d92f32c27 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 14:43:33 +0200 Subject: [PATCH 302/579] Hide scroll speed in effect row attribute --- .../Timing/RowAttributes/EffectRowAttribute.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs index ad22aa81fc7b..253bfdd73a62 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs @@ -5,6 +5,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Screens.Edit.Timing.RowAttributes { @@ -15,6 +17,10 @@ public partial class EffectRowAttribute : RowAttribute private AttributeText kiaiModeBubble = null!; private AttributeText text = null!; + private AttributeProgressBar progressBar = null!; + + [Resolved] + protected EditorBeatmap Beatmap { get; private set; } = null!; public EffectRowAttribute(EffectControlPoint effect) : base(effect, "effect") @@ -28,7 +34,7 @@ private void load() { Content.AddRange(new Drawable[] { - new AttributeProgressBar(Point) + progressBar = new AttributeProgressBar(Point) { Current = scrollSpeed, }, @@ -36,6 +42,14 @@ private void load() kiaiModeBubble = new AttributeText(Point) { Text = "kiai" }, }); + var drawableRuleset = Beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateDrawableRulesetWith(Beatmap.PlayableBeatmap); + + if (drawableRuleset is not IDrawableScrollingRuleset scrollingRuleset || scrollingRuleset.VisualisationMethod == ScrollVisualisationMethod.Constant) + { + text.Hide(); + progressBar.Hide(); + } + kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true); scrollSpeed.BindValueChanged(_ => updateText(), true); } From 3d4bc8a2cc5da3c8985e9d0ef7330ee21e49f311 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 15:04:38 +0200 Subject: [PATCH 303/579] fix tests --- .../Editing/TestScenePlacementBlueprint.cs | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index 8173536ba401..fe74e1b34618 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -96,32 +96,6 @@ public void TestCommitPlacementViaRightClick() AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); } - [Test] - public void TestCommitPlacementViaGlobalAction() - { - Playfield playfield = null!; - - AddStep("select slider placement tool", () => InputManager.Key(Key.Number3)); - AddStep("move mouse to top left of playfield", () => - { - playfield = this.ChildrenOfType().Single(); - var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4; - InputManager.MoveMouseTo(location); - }); - AddStep("begin placement", () => InputManager.Click(MouseButton.Left)); - AddStep("move mouse to bottom right of playfield", () => - { - var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4; - InputManager.MoveMouseTo(location); - }); - AddStep("confirm via global action", () => - { - globalActionContainer.TriggerPressed(GlobalAction.Select); - globalActionContainer.TriggerReleased(GlobalAction.Select); - }); - AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); - } - [Test] public void TestAbortPlacementViaGlobalAction() { @@ -272,11 +246,7 @@ public void TestNodeSamplesAndSamplesAreSame() var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4; InputManager.MoveMouseTo(location); }); - AddStep("confirm via global action", () => - { - globalActionContainer.TriggerPressed(GlobalAction.Select); - globalActionContainer.TriggerReleased(GlobalAction.Select); - }); + AddStep("confirm via right click", () => InputManager.Click(MouseButton.Right)); AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); AddAssert("slider samples have drum bank", () => EditorBeatmap.HitObjects[0].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM)); From d1d195cf18f872b8a57f696d58c12aae1ed31fcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Aug 2024 02:30:59 +0900 Subject: [PATCH 304/579] Fix incorrect skin lookup shortcutting causing sprites to no longer work --- osu.Game/Skinning/ArgonSkin.cs | 8 ++++---- osu.Game/Skinning/LegacySkin.cs | 8 ++++---- osu.Game/Skinning/TrianglesSkin.cs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 85abb1edcd4c..c66df82e0d28 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -94,13 +94,13 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) // Temporary until default skin has a valid hit lighting. if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty(); - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - switch (lookup) { case SkinComponentsContainerLookup containerLookup: + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) + return c; + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.SongSelect: @@ -257,7 +257,7 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) return null; } - return null; + return base.GetDrawableComponent(lookup); } public override IBindable? GetConfig(TLookup lookup) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 8f6e634dd6c7..bbca0178d514 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -356,12 +356,12 @@ protected override void ParseConfigurationStream(Stream stream) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - switch (lookup) { case SkinComponentsContainerLookup containerLookup: + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) + return c; + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: @@ -445,7 +445,7 @@ protected override void ParseConfigurationStream(Stream stream) return null; } - return null; + return base.GetDrawableComponent(lookup); } private Texture? getParticleTexture(HitResult result) diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 29abb1949fcf..7971aee794b3 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -64,12 +64,12 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) // Temporary until default skin has a valid hit lighting. if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty(); - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - switch (lookup) { case SkinComponentsContainerLookup containerLookup: + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) + return c; + // Only handle global level defaults for now. if (containerLookup.Ruleset != null) return null; @@ -178,7 +178,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) return null; } - return null; + return base.GetDrawableComponent(lookup); } public override IBindable? GetConfig(TLookup lookup) From f74263db8111d5abe4825816f938697b00bab562 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 17 Aug 2024 00:59:44 +0300 Subject: [PATCH 305/579] Remove extra box in OnlinePlayBackgroundScreen --- .../Components/OnlinePlayBackgroundScreen.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs index ea422f83e39c..ef7c1747e975 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs @@ -3,10 +3,8 @@ using System.Threading; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; @@ -20,16 +18,6 @@ public abstract partial class OnlinePlayBackgroundScreen : BackgroundScreen private CancellationTokenSource? cancellationSource; private PlaylistItemBackground? background; - protected OnlinePlayBackgroundScreen() - { - AddInternal(new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MinValue, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.9f), Color4.Black.Opacity(0.6f)) - }); - } - [BackgroundDependencyLoader] private void load() { @@ -83,6 +71,7 @@ private void switchBackground(PlaylistItemBackground newBackground) } newBackground.Depth = newDepth; + newBackground.Colour = ColourInfo.GradientVertical(new Color4(0.1f, 0.1f, 0.1f, 1f), new Color4(0.4f, 0.4f, 0.4f, 1f)); newBackground.BlurTo(new Vector2(10)); AddInternal(background = newBackground); From 04a2d67ca4131d56d22a6cf3d6ca2c432726f01b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Aug 2024 15:13:44 +0900 Subject: [PATCH 306/579] Fix legacy combo counter bounce animation not always playing As mentioned [in discord](https://discord.com/channels/188630481301012481/1097318920991559880/1274231995261649006). --- osu.Game/Skinning/LegacyDefaultComboCounter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs index f6333589934e..6c81b1f959a5 100644 --- a/osu.Game/Skinning/LegacyDefaultComboCounter.cs +++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs @@ -41,9 +41,6 @@ protected override void LoadComplete() protected override void OnCountIncrement() { - scheduledPopOut?.Cancel(); - scheduledPopOut = null; - DisplayedCountText.Show(); PopOutCountText.Text = FormatCount(Current.Value); From 3cd5820b5b903227d80b24ae57faa8996467ceed Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 17 Aug 2024 10:34:39 +0300 Subject: [PATCH 307/579] Make PositionSnapGrid a BufferedContainer --- .../Compose/Components/PositionSnapGrid.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index e576ac1e49d9..cbdf02488a0d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -2,15 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract partial class PositionSnapGrid : CompositeDrawable + public abstract partial class PositionSnapGrid : BufferedContainer { /// /// The position of the origin of this in local coordinates. @@ -20,7 +22,10 @@ public abstract partial class PositionSnapGrid : CompositeDrawable protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); protected PositionSnapGrid() + : base(cachedFrameBuffer: true) { + BackgroundColour = Color4.White.Opacity(0); + StartPosition.BindValueChanged(_ => GridCache.Invalidate()); AddLayout(GridCache); @@ -30,7 +35,8 @@ protected override void Update() { base.Update(); - if (GridCache.IsValid) return; + if (GridCache.IsValid) + return; ClearInternal(); @@ -38,6 +44,7 @@ protected override void Update() CreateContent(); GridCache.Validate(); + ForceRedraw(); } protected abstract void CreateContent(); @@ -53,7 +60,6 @@ protected void GenerateOutline(Vector2 drawSize) { Colour = Colour4.White, Alpha = 0.3f, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Height = lineWidth, Y = 0, @@ -62,28 +68,26 @@ protected void GenerateOutline(Vector2 drawSize) { Colour = Colour4.White, Alpha = 0.3f, - Origin = Anchor.CentreLeft, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, + Height = lineWidth }, new Box { Colour = Colour4.White, Alpha = 0.3f, - Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, + Width = lineWidth }, new Box { Colour = Colour4.White, Alpha = 0.3f, - Origin = Anchor.TopCentre, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, + Width = lineWidth }, }); } From 6dd08e9a964a978d715c6dd7fe148e7392f8aa73 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 17 Aug 2024 11:26:46 -0700 Subject: [PATCH 308/579] Fix beatmap carousel panels not blocking hover of other panels in song select --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4c9ac57d9d52..755008d37096 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -166,6 +166,8 @@ protected override bool OnClick(ClickEvent e) return true; } + protected override bool OnHover(HoverEvent e) => true; + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From e75ae4a37bc0c427e73e975a48a7cb92b067db0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 04:04:24 +0900 Subject: [PATCH 309/579] More hardening of `TestMultiplayerClient` to attempt to fix test failures --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 4c3deac1d7d3..efa9dc499061 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -208,6 +208,9 @@ public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBea protected override async Task JoinRoom(long roomId, string? password = null) { + if (RoomJoined || ServerAPIRoom != null) + throw new InvalidOperationException("Already joined a room"); + roomId = clone(roomId); password = clone(password); @@ -260,6 +263,8 @@ protected override void OnRoomJoined() protected override Task LeaveRoomInternal() { RoomJoined = false; + ServerAPIRoom = null; + ServerRoom = null; return Task.CompletedTask; } From 95d06333c1d948d6d8372bd8b708fc2b38a6817c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 13:49:59 +0900 Subject: [PATCH 310/579] Fix typo in editor field --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3c1d0fbb1c52..484fbd5084e2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -71,7 +71,7 @@ protected override IEnumerable CreateTernaryButtons() protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup(); [Cached] - protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); + protected readonly FreehandSliderToolboxGroup FreehandSliderToolboxGroup = new FreehandSliderToolboxGroup(); [BackgroundDependencyLoader] private void load() @@ -110,7 +110,7 @@ private void load() RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler, }, - FreehandlSliderToolboxGroup + FreehandSliderToolboxGroup } ); } From 9e962ce314f188c4bf1f17db7510cb4bf62236ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 19 Aug 2024 14:14:12 +0900 Subject: [PATCH 311/579] Add failing test case --- .../Gameplay/TestScenePauseInputHandling.cs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index bc66947ccd2b..843e924660bd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -47,6 +47,11 @@ public partial class TestScenePauseInputHandling : PlayerTestScene { Position = OsuPlayfield.BASE_SIZE / 2, StartTime = 5000, + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 10000, } } }; @@ -281,6 +286,38 @@ public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked() AddAssert("button is released in kbc", () => !Player.DrawableRuleset.Playfield.FindClosestParent()!.PressedActions.Any()); } + [Test] + public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked_PauseWhileHolding() + { + KeyCounter counter = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1)); + + AddStep("pause", () => Player.Pause()); + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + + checkKey(() => counter, 1, false); + + seekTo(5000); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + + checkKey(() => counter, 2, true); + AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(2)); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 2, false); + } + private void loadPlayer(Func createRuleset) { AddStep("set ruleset", () => currentRuleset = createRuleset()); @@ -288,12 +325,17 @@ private void loadPlayer(Func createRuleset) AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType().All(s => s.ComponentsLoaded)); - AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0)); - AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500)); + seekTo(0); AddAssert("not in break", () => !Player.IsBreakTime.Value); AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield)); } + private void seekTo(double time) + { + AddStep($"seek to {time}ms", () => Player.GameplayClockContainer.Seek(time)); + AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(500)); + } + private void checkKey(Func counter, int count, bool active) { AddAssert($"key count = {count}", () => counter().CountPresses.Value, () => Is.EqualTo(count)); From 62dec1cd786717eb6c2f575dd80aca9c85be8967 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 19 Aug 2024 14:14:39 +0900 Subject: [PATCH 312/579] Fix oversight in input blocking from osu! gameplay resume --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index d90d3d26eb3e..b12895ae522b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK.Graphics; +using TagLib.Flac; namespace osu.Game.Rulesets.Osu.UI { @@ -172,13 +173,14 @@ public OsuResumeOverlayInputBlocker() Depth = float.MinValue; } - public bool OnPressed(KeyBindingPressEvent e) + protected override void Update() { - bool block = BlockNextPress; + base.Update(); BlockNextPress = false; - return block; } + public bool OnPressed(KeyBindingPressEvent e) => BlockNextPress; + public void OnReleased(KeyBindingReleaseEvent e) { } From 4a3f4c3a55ac513b85d1d1434b4ac145e3e53e78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 14:46:36 +0900 Subject: [PATCH 313/579] Don't duck music when effect volume is set to zero Addresses https://github.com/ppy/osu/discussions/28984. --- osu.Game/Overlays/MusicController.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d9bb92b4b700..27c7cd0f49af 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -60,6 +60,8 @@ public partial class MusicController : CompositeDrawable [Resolved] private RealmAccess realm { get; set; } = null!; + private BindableNumber sampleVolume = null!; + private readonly BindableDouble audioDuckVolume = new BindableDouble(1); private AudioFilter audioDuckFilter = null!; @@ -69,6 +71,7 @@ private void load(AudioManager audio) { AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer)); audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume); + sampleVolume = audio.VolumeSample.GetBoundCopy(); } protected override void LoadComplete() @@ -269,6 +272,10 @@ public void NextTrack(Action? onSuccess = null, bool allowProtectedTracks = fals /// A which will restore the duck operation when disposed. public IDisposable Duck(DuckParameters? parameters = null) { + // Don't duck if samples have no volume, it sounds weird. + if (sampleVolume.Value == 0) + return new InvokeOnDisposal(() => { }); + parameters ??= new DuckParameters(); duckOperations.Add(parameters); @@ -302,6 +309,10 @@ void restoreDucking() => Schedule(() => /// Parameters defining the ducking operation. public void DuckMomentarily(double delayUntilRestore, DuckParameters? parameters = null) { + // Don't duck if samples have no volume, it sounds weird. + if (sampleVolume.Value == 0) + return; + parameters ??= new DuckParameters(); IDisposable duckOperation = Duck(parameters); From ca92c116b5acc75945278fffc46d0d49d651ca9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 15:01:11 +0900 Subject: [PATCH 314/579] Fix osu!catch trail spacing not matching osu!stable expectations Closes https://github.com/ppy/osu/issues/28997. --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 21faec56de48..338e1364a980 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -110,9 +110,9 @@ protected override void UpdateAfterChildren() if (Catcher.Dashing || Catcher.HyperDashing) { - double generationInterval = Catcher.HyperDashing ? 25 : 50; + const double trail_generation_interval = 16; - if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval) + if (Time.Current - catcherTrails.LastDashTrailTime >= trail_generation_interval) displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing); } From 86d0079dcdadbbf1521dc3d8520616b8bb27a529 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 19 Aug 2024 15:43:57 +0900 Subject: [PATCH 315/579] Rewrite the fix to look less hacky and direct to the point --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index b12895ae522b..8ae08ed0211c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -36,9 +37,11 @@ private void load() { OsuResumeOverlayInputBlocker? inputBlocker = null; - if (drawableRuleset != null) + var drawableOsuRuleset = (DrawableOsuRuleset?)drawableRuleset; + + if (drawableOsuRuleset != null) { - var osuPlayfield = (OsuPlayfield)drawableRuleset.Playfield; + var osuPlayfield = drawableOsuRuleset.Playfield; osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker()); } @@ -46,13 +49,14 @@ private void load() { Child = clickToResumeCursor = new OsuClickToResumeCursor { - ResumeRequested = () => + ResumeRequested = action => { // since the user had to press a button to tap the resume cursor, // block that press event from potentially reaching a hit circle that's behind the cursor. // we cannot do this from OsuClickToResumeCursor directly since we're in a different input manager tree than the gameplay one, // so we rely on a dedicated input blocking component that's implanted in there to do that for us. - if (inputBlocker != null) + // note this only matters when the user didn't pause while they were holding the same key that they are resuming with. + if (inputBlocker != null && !drawableOsuRuleset.AsNonNull().KeyBindingInputManager.PressedActions.Contains(action)) inputBlocker.BlockNextPress = true; Resume(); @@ -95,7 +99,7 @@ public partial class OsuClickToResumeCursor : OsuCursor, IKeyBindingHandler true; - public Action? ResumeRequested; + public Action? ResumeRequested; private Container scaleTransitionContainer = null!; public OsuClickToResumeCursor() @@ -137,7 +141,7 @@ public bool OnPressed(KeyBindingPressEvent e) return false; scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); - ResumeRequested?.Invoke(); + ResumeRequested?.Invoke(e.Action); return true; } @@ -173,14 +177,13 @@ public OsuResumeOverlayInputBlocker() Depth = float.MinValue; } - protected override void Update() + public bool OnPressed(KeyBindingPressEvent e) { - base.Update(); + bool block = BlockNextPress; BlockNextPress = false; + return block; } - public bool OnPressed(KeyBindingPressEvent e) => BlockNextPress; - public void OnReleased(KeyBindingReleaseEvent e) { } From 2a49167aa0051bc491d374de9a0b05c61daf12e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 19 Aug 2024 15:44:17 +0900 Subject: [PATCH 316/579] Remove flac whatever --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 8ae08ed0211c..b045b829607a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK.Graphics; -using TagLib.Flac; namespace osu.Game.Rulesets.Osu.UI { From 1bd2f4c6a2a2c77411d2bf74ec3e4a408f82ed59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 15:45:18 +0900 Subject: [PATCH 317/579] Fix skin editor components sidebar not reloading when changing skins Closes https://github.com/ppy/osu/issues/29098. --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 484af34603ff..03acf1e68c6c 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -421,6 +421,9 @@ private void skinChanged() if (targetContainer != null) changeHandler = new SkinEditorChangeHandler(targetContainer); hasBegunMutating = true; + + // Reload sidebar components. + selectedTarget.TriggerChange(); } /// From 005b1038a3e31092cdf8174bc42ddfe6f497ef25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 20:23:25 +0900 Subject: [PATCH 318/579] Change "hold for menu" button to only show for touch by default --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Localisation/GameplaySettingsStrings.cs | 5 +++++ .../Settings/Sections/Gameplay/HUDSettings.cs | 5 +++++ osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 16 ++++++++++++++-- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d00856dd8057..8d6c244b350c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -205,6 +205,8 @@ protected override void InitialiseDefaults() SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowTicks, true); + + SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false); } protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup) @@ -429,5 +431,6 @@ public enum OsuSetting HideCountryFlags, EditorTimelineShowTimingChanges, EditorTimelineShowTicks, + AlwaysShowHoldForMenuButton } } diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 8ee76fdd5519..6de61f7ebeae 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -84,6 +84,11 @@ public static class GameplaySettingsStrings /// public static LocalisableString AlwaysShowGameplayLeaderboard => new TranslatableString(getKey(@"gameplay_leaderboard"), @"Always show gameplay leaderboard"); + /// + /// "Always show hold for menu button" + /// + public static LocalisableString AlwaysShowHoldForMenuButton => new TranslatableString(getKey(@"always_show_hold_for_menu_button"), @"Always show hold for menu button"); + /// /// "Always play first combo break sound" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index 3e67b2f10331..f4dd319152c0 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -41,6 +41,11 @@ private void load(OsuConfigManager config) Current = config.GetBindable(OsuSetting.GameplayLeaderboard), }, new SettingsCheckbox + { + LabelText = GameplaySettingsStrings.AlwaysShowHoldForMenuButton, + Current = config.GetBindable(OsuSetting.AlwaysShowHoldForMenuButton), + }, + new SettingsCheckbox { ClassicDefault = false, LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail, diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 6d045e5f01cb..41600c2bb8e9 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -40,6 +40,10 @@ public partial class HoldForMenuButton : FillFlowContainer private OsuSpriteText text; + private Bindable alwaysShow; + + public override bool PropagatePositionalInputSubTree => alwaysShow.Value || touchActive.Value; + public HoldForMenuButton() { Direction = FillDirection.Horizontal; @@ -50,7 +54,7 @@ public HoldForMenuButton() } [BackgroundDependencyLoader(true)] - private void load(Player player) + private void load(Player player, OsuConfigManager config) { Children = new Drawable[] { @@ -71,6 +75,8 @@ private void load(Player player) }; AutoSizeAxes = Axes.Both; + + alwaysShow = config.GetBindable(OsuSetting.AlwaysShowHoldForMenuButton); } [Resolved] @@ -119,7 +125,9 @@ protected override void Update() if (text.Alpha > 0 || button.Progress.Value > 0 || button.IsHovered) Alpha = 1; - else + else if (touchActive.Value) + Alpha = 0.08f; + else if (alwaysShow.Value) { float minAlpha = touchActive.Value ? .08f : 0; @@ -127,6 +135,10 @@ protected override void Update() Math.Clamp(Clock.ElapsedFrameTime, 0, 200), Alpha, Math.Clamp(1 - positionalAdjust, minAlpha, 1), 0, 200, Easing.OutQuint); } + else + { + Alpha = 0; + } } private partial class HoldButton : HoldToConfirmContainer, IKeyBindingHandler From 6985e2e657c4ed875aa8305f4a5d8f7fab651d1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 20:28:02 +0900 Subject: [PATCH 319/579] Increase default visibility on touch platforms --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 41600c2bb8e9..89d083eca994 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -30,6 +30,8 @@ public partial class HoldForMenuButton : FillFlowContainer { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public override bool PropagatePositionalInputSubTree => alwaysShow.Value || touchActive.Value; + public readonly Bindable IsPaused = new Bindable(); public readonly Bindable ReplayLoaded = new Bindable(); @@ -42,8 +44,6 @@ public partial class HoldForMenuButton : FillFlowContainer private Bindable alwaysShow; - public override bool PropagatePositionalInputSubTree => alwaysShow.Value || touchActive.Value; - public HoldForMenuButton() { Direction = FillDirection.Horizontal; @@ -123,10 +123,13 @@ protected override void Update() { base.Update(); + // While the button is hovered or still animating, keep fully visible. if (text.Alpha > 0 || button.Progress.Value > 0 || button.IsHovered) Alpha = 1; + // When touch input is detected, keep visible at a constant opacity. else if (touchActive.Value) - Alpha = 0.08f; + Alpha = 0.5f; + // Otherwise, if the user chooses, show it when the mouse is nearby. else if (alwaysShow.Value) { float minAlpha = touchActive.Value ? .08f : 0; @@ -136,9 +139,7 @@ protected override void Update() Alpha, Math.Clamp(1 - positionalAdjust, minAlpha, 1), 0, 200, Easing.OutQuint); } else - { Alpha = 0; - } } private partial class HoldButton : HoldToConfirmContainer, IKeyBindingHandler From 610ebc5481ebc605ce06d5537e8ad4355c517cd6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 20:50:11 +0900 Subject: [PATCH 320/579] Fix toolbar PP change showing `+0` instead of `0` --- osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs | 2 +- .../Toolbar/TransientUserStatisticsUpdateDisplay.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs index 1a4ca6597524..a81c940d82ad 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs @@ -142,7 +142,7 @@ public void TestTransientUserStatisticsDisplay() new UserStatistics { GlobalRank = 111_111, - PP = 1357 + PP = 1357.1m }); }); AddStep("Was null", () => diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index c6f373d55f89..a25df0830973 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -24,7 +24,7 @@ public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable public Bindable LatestUpdate { get; } = new Bindable(); private Statistic globalRank = null!; - private Statistic pp = null!; + private Statistic pp = null!; [BackgroundDependencyLoader] private void load(UserStatisticsWatcher? userStatisticsWatcher) @@ -43,7 +43,7 @@ private void load(UserStatisticsWatcher? userStatisticsWatcher) Children = new Drawable[] { globalRank = new Statistic(UsersStrings.ShowRankGlobalSimple, @"#", Comparer.Create((before, after) => before - after)), - pp = new Statistic(RankingsStrings.StatPerformance, string.Empty, Comparer.Create((before, after) => Math.Sign(after - before))), + pp = new Statistic(RankingsStrings.StatPerformance, string.Empty, Comparer.Create((before, after) => Math.Sign(after - before))), } }; @@ -83,7 +83,7 @@ protected override void LoadComplete() } if (update.After.PP != null) - pp.Display(update.Before.PP ?? update.After.PP.Value, Math.Abs((update.After.PP - update.Before.PP) ?? 0M), update.After.PP.Value); + pp.Display((int)(update.Before.PP ?? update.After.PP.Value), (int)Math.Abs((update.After.PP - update.Before.PP) ?? 0M), (int)update.After.PP.Value); this.Delay(5000).FadeOut(500, Easing.OutQuint); }); From 67de43213c4a097dcf211d42549fd86b4f89133f Mon Sep 17 00:00:00 2001 From: TheOmyNomy Date: Mon, 19 Aug 2024 23:21:06 +1000 Subject: [PATCH 321/579] Apply current cursor expansion scale to trail parts --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 15 +++++++++++---- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 5 +++++ .../UI/Cursor/OsuCursorContainer.cs | 13 ++++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 6452444fedf1..a4bccb0affc4 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -38,6 +38,11 @@ public partial class CursorTrail : Drawable, IRequireHighFrequencyMousePosition private double timeOffset; private float time; + /// + /// The scale used on creation of a new trail part. + /// + public Vector2 NewPartScale = Vector2.One; + private Anchor trailOrigin = Anchor.Centre; protected Anchor TrailOrigin @@ -188,6 +193,7 @@ private void addPart(Vector2 localSpacePosition) { parts[currentIndex].Position = localSpacePosition; parts[currentIndex].Time = time + 1; + parts[currentIndex].Scale = NewPartScale; ++parts[currentIndex].InvalidationID; currentIndex = (currentIndex + 1) % max_sprites; @@ -199,6 +205,7 @@ private struct TrailPart { public Vector2 Position; public float Time; + public Vector2 Scale; public long InvalidationID; } @@ -280,7 +287,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)), + Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), TexturePosition = textureRect.BottomLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, @@ -289,7 +296,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)), + Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), TexturePosition = textureRect.BottomRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, @@ -298,7 +305,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y - texture.DisplayHeight * originPosition.Y), + Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), TexturePosition = textureRect.TopRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, @@ -307,7 +314,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y - texture.DisplayHeight * originPosition.Y), + Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), TexturePosition = textureRect.TopLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index d8f50c1f5db6..0bb316e0aa50 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -31,6 +31,11 @@ public partial class OsuCursor : SkinReloadableDrawable private SkinnableCursor skinnableCursor => (SkinnableCursor)cursorSprite.Drawable; + /// + /// The current expanded scale of the cursor. + /// + public Vector2 CurrentExpandedScale => skinnableCursor.ExpandTarget?.Scale ?? Vector2.One; + public IBindable CursorScale => cursorScale; private readonly Bindable cursorScale = new BindableFloat(1); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index ba8a634ff742..9ac81d13a7da 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -23,14 +23,13 @@ public partial class OsuCursorContainer : GameplayCursorContainer, IKeyBindingHa public new OsuCursor ActiveCursor => (OsuCursor)base.ActiveCursor; protected override Drawable CreateCursor() => new OsuCursor(); - protected override Container Content => fadeContainer; private readonly Container fadeContainer; private readonly Bindable showTrail = new Bindable(true); - private readonly Drawable cursorTrail; + private readonly SkinnableDrawable cursorTrail; private readonly CursorRippleVisualiser rippleVisualiser; @@ -39,7 +38,7 @@ public OsuCursorContainer() InternalChild = fadeContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new[] + Children = new CompositeDrawable[] { cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling), rippleVisualiser = new CursorRippleVisualiser(), @@ -79,6 +78,14 @@ private void updateExpandedState() ActiveCursor.Contract(); } + protected override void Update() + { + base.Update(); + + // We can direct cast here because the cursor trail is always a derived class of CursorTrail. + ((CursorTrail)cursorTrail.Drawable).NewPartScale = ActiveCursor.CurrentExpandedScale; + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) From 59ba48bc8130cd6b96128df531d685698010e3f6 Mon Sep 17 00:00:00 2001 From: Layendan Date: Mon, 19 Aug 2024 07:58:20 -0700 Subject: [PATCH 322/579] Fix crash if favourite button api request fails --- osu.Game/Screens/Ranking/FavouriteButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index daa6312020a3..bb4f25080c53 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -78,7 +78,7 @@ private void getBeatmapSet() { Logger.Error(e, $"Failed to fetch beatmap info: {e.Message}"); - loading.Hide(); + Schedule(() => loading.Hide()); Enabled.Value = false; }; api.Queue(beatmapSetRequest); From 5ba1b4fe3d16bd95204137857e20cf343f5e701a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 01:12:57 +0900 Subject: [PATCH 323/579] Update test coverage --- .../Visual/Gameplay/TestSceneHoldForMenuButton.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index 3c225d60e049..cd1334165b3c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -1,13 +1,13 @@ // 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.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -21,11 +21,19 @@ public partial class TestSceneHoldForMenuButton : OsuManualInputManagerTestScene protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms - private HoldForMenuButton holdForMenuButton; + private HoldForMenuButton holdForMenuButton = null!; + + [Resolved] + private OsuConfigManager config { get; set; } = null!; [SetUpSteps] public void SetUpSteps() { + AddStep("set button always on", () => + { + config.SetValue(OsuSetting.AlwaysShowHoldForMenuButton, true); + }); + AddStep("create button", () => { exitAction = false; From 86c3c115f6fbe315a4ef99c9218b73239e703573 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 12:15:33 +0900 Subject: [PATCH 324/579] Make grid/distance snap binds T/Y respectively --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index bbcf4fa2d4f1..4476160f8130 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -54,11 +54,8 @@ protected override DrawableRuleset CreateDrawableRuleset(Ruleset r protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons() - .Concat(DistanceSnapProvider.CreateTernaryButtons()) - .Concat(new[] - { - new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }) - }); + .Append(new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap })) + .Concat(DistanceSnapProvider.CreateTernaryButtons()); private BindableList selectedHitObjects; From a3234e2cdefaca43ca0aa76a092bc1bda00a156f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Aug 2024 12:28:36 +0900 Subject: [PATCH 325/579] Add failing test case --- .../Skinning/ManiaSkinnableTestScene.cs | 10 +-- .../Skinning/TestSceneComboCounter.cs | 75 ++++++++++++++++--- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index abf01aa4a4ee..b2e8ebd5812d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning public abstract partial class ManiaSkinnableTestScene : SkinnableTestScene { [Cached(Type = typeof(IScrollingInfo))] - private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + protected readonly TestScrollingInfo ScrollingInfo = new TestScrollingInfo(); [Cached] private readonly StageDefinition stage = new StageDefinition(4); @@ -30,7 +30,7 @@ public abstract partial class ManiaSkinnableTestScene : SkinnableTestScene protected ManiaSkinnableTestScene() { - scrollingInfo.Direction.Value = ScrollingDirection.Down; + ScrollingInfo.Direction.Value = ScrollingDirection.Down; Add(new Box { @@ -43,16 +43,16 @@ protected ManiaSkinnableTestScene() [Test] public void TestScrollingDown() { - AddStep("change direction to down", () => scrollingInfo.Direction.Value = ScrollingDirection.Down); + AddStep("change direction to down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down); } [Test] public void TestScrollingUp() { - AddStep("change direction to up", () => scrollingInfo.Direction.Value = ScrollingDirection.Up); + AddStep("change direction to up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up); } - private class TestScrollingInfo : IScrollingInfo + protected class TestScrollingInfo : IScrollingInfo { public readonly Bindable Direction = new Bindable(); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs index c1e1cfd7aff7..ccdebb502c5d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs @@ -1,13 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Skinning.Argon; using osu.Game.Rulesets.Mania.Skinning.Legacy; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Tests.Skinning @@ -17,22 +21,75 @@ public partial class TestSceneComboCounter : ManiaSkinnableTestScene [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset()); - [SetUpSteps] - public void SetUpSteps() + [Test] + public void TestDisplay() { - AddStep("setup", () => SetContents(s => + setup(Anchor.Centre); + AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20); + AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss })); + } + + [Test] + public void TestAnchorOrigin() + { + AddStep("set direction down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down); + setup(Anchor.TopCentre, 20); + AddStep("set direction up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up); + check(Anchor.BottomCentre, -20); + + AddStep("set direction up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up); + setup(Anchor.BottomCentre, -20); + AddStep("set direction down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down); + check(Anchor.TopCentre, 20); + + AddStep("set direction down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down); + setup(Anchor.Centre, 20); + AddStep("set direction up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up); + check(Anchor.Centre, 20); + + AddStep("set direction up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up); + setup(Anchor.Centre, -20); + AddStep("set direction down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down); + check(Anchor.Centre, -20); + } + + private void setup(Anchor anchor, float y = 0) + { + AddStep($"setup {anchor} {y}", () => SetContents(s => { + var container = new Container + { + RelativeSizeAxes = Axes.Both, + }; + if (s is ArgonSkin) - return new ArgonManiaComboCounter(); + container.Add(new ArgonManiaComboCounter()); + else if (s is LegacySkin) + container.Add(new LegacyManiaComboCounter()); + else + container.Add(new LegacyManiaComboCounter()); - if (s is LegacySkin) - return new LegacyManiaComboCounter(); + container.Child.Anchor = anchor; + container.Child.Origin = Anchor.Centre; + container.Child.Y = y; - return new LegacyManiaComboCounter(); + return container; })); + } - AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20); - AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss })); + private void check(Anchor anchor, float y) + { + AddAssert($"check {anchor} {y}", () => + { + foreach (var combo in this.ChildrenOfType()) + { + var drawableCombo = (Drawable)combo; + if (drawableCombo.Anchor != anchor || drawableCombo.Y != y) + return false; + } + + return true; + }); } } } From 4d74625bc7cf278bf273b7c5e51f5df4e8fdb759 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Aug 2024 12:39:51 +0900 Subject: [PATCH 326/579] Fix mania combo counter positioning break on centre anchor --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 10 +++++----- .../Skinning/Legacy/LegacyManiaComboCounter.cs | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 5b23cea49623..6626e5f1c768 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -38,11 +38,11 @@ protected override void LoadComplete() private void updateAnchor() { // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction - if (!Anchor.HasFlag(Anchor.y1)) - { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; - } + if (Anchor.HasFlag(Anchor.y1)) + return; + + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; // change the sign of the Y coordinate in line with the scrolling direction. // i.e. if the user changes direction from down to up, the anchor is changed from top to bottom, and the Y is flipped from positive to negative here. diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 5832210836df..07d014b416fe 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -44,16 +45,15 @@ protected override void LoadComplete() private void updateAnchor() { // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction - if (!Anchor.HasFlag(Anchor.y1)) - { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; - } + if (Anchor.HasFlag(Anchor.y1)) + return; + + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; - // since we flip the vertical anchor when changing scroll direction, - // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. - if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) - Y = -Y; + // change the sign of the Y coordinate in line with the scrolling direction. + // i.e. if the user changes direction from down to up, the anchor is changed from top to bottom, and the Y is flipped from positive to negative here. + Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1); } protected override void OnCountIncrement() From 180c4a02485398dd6af523c4665476aa51a1665e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 14:20:52 +0900 Subject: [PATCH 327/579] Fix tests by removing assumption --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 9ac81d13a7da..8c0871d54fff 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -82,8 +82,8 @@ protected override void Update() { base.Update(); - // We can direct cast here because the cursor trail is always a derived class of CursorTrail. - ((CursorTrail)cursorTrail.Drawable).NewPartScale = ActiveCursor.CurrentExpandedScale; + if (cursorTrail.Drawable is CursorTrail trail) + trail.NewPartScale = ActiveCursor.CurrentExpandedScale; } public bool OnPressed(KeyBindingPressEvent e) From 4a19ed7472f27859ef47dc2907c617c33b786365 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 15:20:48 +0900 Subject: [PATCH 328/579] Add test --- .../TestSceneCursorTrail.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 4db66fde4b31..17f365f820c4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -88,6 +88,21 @@ public void TestLegacyDisjointCursorTrailViaNoCursor() AddAssert("trail is disjoint", () => this.ChildrenOfType().Single().DisjointTrail, () => Is.True); } + [Test] + public void TestClickExpand() + { + createTest(() => new Container + { + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(10), + Child = new CursorTrail(), + }); + + AddStep("expand", () => this.ChildrenOfType().Single().NewPartScale = new Vector2(3)); + AddWaitStep("let the cursor trail draw a bit", 5); + AddStep("contract", () => this.ChildrenOfType().Single().NewPartScale = Vector2.One); + } + private void createTest(Func createContent) => AddStep("create trail", () => { Clear(); From 2e67ff1d92fa25d4faf231b1d26403926ae92773 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 16:14:05 +0900 Subject: [PATCH 329/579] Fix tests --- .../Editor/TestSceneOsuEditorGrids.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index b17f4e74877c..b70ecfbba8e1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -24,24 +24,24 @@ public partial class TestSceneOsuEditorGrids : EditorTestScene [Test] public void TestGridToggles() { - AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("enable distance snap grid", () => InputManager.Key(Key.Y)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); gridActive(false); - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddStep("enable rectangular grid", () => InputManager.Key(Key.T)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any()); gridActive(true); - AddStep("disable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("disable distance snap grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); gridActive(true); - AddStep("disable rectangular grid", () => InputManager.Key(Key.Y)); + AddStep("disable rectangular grid", () => InputManager.Key(Key.T)); AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType().Any()); gridActive(false); } @@ -57,7 +57,7 @@ public void TestDistanceSnapMomentaryToggle() AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); - AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("enable distance snap grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); @@ -70,7 +70,7 @@ public void TestDistanceSnapAdjustDoesNotHideTheGridIfStartingEnabled() { double distanceSnap = double.PositiveInfinity; - AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("enable distance snap grid", () => InputManager.Key(Key.Y)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); @@ -170,7 +170,7 @@ private Vector2 uniqueSnappingPosition(PositionSnapGrid grid) [Test] public void TestGridSizeToggling() { - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddStep("enable rectangular grid", () => InputManager.Key(Key.T)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); gridSizeIs(4); From 2ecf5ec939d2eb5eb12a91fe846365392a8af6a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Aug 2024 16:22:25 +0900 Subject: [PATCH 330/579] Add further test coverage --- .../Gameplay/TestScenePauseInputHandling.cs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index 843e924660bd..8a41d8b573f3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -52,6 +52,11 @@ public partial class TestScenePauseInputHandling : PlayerTestScene { Position = OsuPlayfield.BASE_SIZE / 2, StartTime = 10000, + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 15000, } } }; @@ -261,7 +266,7 @@ public void TestManiaHeldInputRemainHeldAfterResume() } [Test] - public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked() + public void TestOsuHitCircleNotReceivingInputOnResume() { KeyCounter counter = null!; @@ -287,7 +292,7 @@ public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked() } [Test] - public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked_PauseWhileHolding() + public void TestOsuHitCircleNotReceivingInputOnResume_PauseWhileHoldingSameKey() { KeyCounter counter = null!; @@ -318,6 +323,32 @@ public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked_PauseW checkKey(() => counter, 2, false); } + [Test] + public void TestOsuHitCircleNotReceivingInputOnResume_PauseWhileHoldingOtherKey() + { + loadPlayer(() => new OsuRuleset()); + + AddStep("press X", () => InputManager.PressKey(Key.X)); + AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1)); + + seekTo(5000); + + AddStep("pause", () => Player.Pause()); + AddStep("release X", () => InputManager.ReleaseKey(Key.X)); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + + AddAssert("circle not hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1)); + + AddStep("press X", () => InputManager.PressKey(Key.X)); + AddStep("release X", () => InputManager.ReleaseKey(Key.X)); + + AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(2)); + } + private void loadPlayer(Func createRuleset) { AddStep("set ruleset", () => currentRuleset = createRuleset()); From 373ff47a94ac29fed06f5c49dd6d5ff438e8fe74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 09:53:40 +0200 Subject: [PATCH 331/579] Remove dead row attribute classes These aren't shown on the control point table since difficulty and sample control points were moved into objects. --- .../Screens/Edit/Timing/ControlPointTable.cs | 6 -- .../RowAttributes/DifficultyRowAttribute.cs | 44 -------------- .../RowAttributes/SampleRowAttribute.cs | 57 ------------------- 3 files changed, 107 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs delete mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 2204fabf572d..8dc0ced30e42 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -323,14 +323,8 @@ private Drawable createAttribute(ControlPoint controlPoint) case TimingControlPoint timing: return new TimingRowAttribute(timing); - case DifficultyControlPoint difficulty: - return new DifficultyRowAttribute(difficulty); - case EffectControlPoint effect: return new EffectRowAttribute(effect); - - case SampleControlPoint sample: - return new SampleRowAttribute(sample); } throw new ArgumentOutOfRangeException(nameof(controlPoint), $"Control point type {controlPoint.GetType()} is not supported"); diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs deleted file mode 100644 index 43f3739503f9..000000000000 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Screens.Edit.Timing.RowAttributes -{ - public partial class DifficultyRowAttribute : RowAttribute - { - private readonly BindableNumber speedMultiplier; - - private OsuSpriteText text = null!; - - public DifficultyRowAttribute(DifficultyControlPoint difficulty) - : base(difficulty, "difficulty") - { - speedMultiplier = difficulty.SliderVelocityBindable.GetBoundCopy(); - } - - [BackgroundDependencyLoader] - private void load() - { - Content.AddRange(new Drawable[] - { - new AttributeProgressBar(Point) - { - Current = speedMultiplier, - }, - text = new AttributeText(Point) - { - Width = 45, - }, - }); - - speedMultiplier.BindValueChanged(_ => updateText(), true); - } - - private void updateText() => text.Text = $"{speedMultiplier.Value:n2}x"; - } -} diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs deleted file mode 100644 index e86a991521cd..000000000000 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Screens.Edit.Timing.RowAttributes -{ - public partial class SampleRowAttribute : RowAttribute - { - private AttributeText sampleText = null!; - private OsuSpriteText volumeText = null!; - - private readonly Bindable sampleBank; - private readonly BindableNumber volume; - - public SampleRowAttribute(SampleControlPoint sample) - : base(sample, "sample") - { - sampleBank = sample.SampleBankBindable.GetBoundCopy(); - volume = sample.SampleVolumeBindable.GetBoundCopy(); - } - - [BackgroundDependencyLoader] - private void load() - { - AttributeProgressBar progress; - - Content.AddRange(new Drawable[] - { - sampleText = new AttributeText(Point), - progress = new AttributeProgressBar(Point), - volumeText = new AttributeText(Point) - { - Width = 40, - }, - }); - - volume.BindValueChanged(vol => - { - progress.Current.Value = vol.NewValue / 100f; - updateText(); - }, true); - - sampleBank.BindValueChanged(_ => updateText(), true); - } - - private void updateText() - { - volumeText.Text = $"{volume.Value}%"; - sampleText.Text = $"{sampleBank.Value}"; - } - } -} From c85b04bca5854e3f6cab6bf79aca17de1a2d1d77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 17:11:22 +0900 Subject: [PATCH 332/579] Add more test coverage to better show overlapping break / kiai sections --- .../Visual/Editing/TestSceneEditorSummaryTimeline.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index ddca2f85533f..677d3135ba8a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -24,7 +24,10 @@ public TestSceneEditorSummaryTimeline() beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = 100 }); beatmap.ControlPointInfo.Add(50000, new DifficultyControlPoint { SliderVelocity = 2 }); + beatmap.ControlPointInfo.Add(80000, new EffectControlPoint { KiaiMode = true }); + beatmap.ControlPointInfo.Add(110000, new EffectControlPoint { KiaiMode = false }); beatmap.BeatmapInfo.Bookmarks = new[] { 75000, 125000 }; + beatmap.Breaks.Add(new ManualBreakPeriod(90000, 120000)); editorBeatmap = new EditorBeatmap(beatmap); } From bccc797bcb0ac6598af5ac4145d71cb9b84664cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 17:45:37 +0900 Subject: [PATCH 333/579] Move break display to background of summary timeline --- .../Components/Timelines/Summary/Parts/BreakPart.cs | 6 +++--- .../Components/Timelines/Summary/SummaryTimeline.cs | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 3cff976f72e8..be3a7b726854 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -69,9 +69,9 @@ private void load(OsuColour colours) RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Both; - InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; - Colour = colours.Gray7; - Alpha = 0.8f; + InternalChild = new Box { RelativeSizeAxes = Axes.Both }; + Colour = colours.Gray5; + Alpha = 0.4f; } public LocalisableString TooltipText => $"{breakPeriod.StartTime.ToEditorFormattedString()} - {breakPeriod.EndTime.ToEditorFormattedString()} break time"; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index a495442c1d46..4ab7c8817821 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -59,6 +59,12 @@ private void load(OverlayColourProvider colourProvider) RelativeSizeAxes = Axes.Both, Height = 0.4f, }, + new BreakPart + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, new ControlPointPart { Anchor = Anchor.Centre, @@ -73,13 +79,6 @@ private void load(OverlayColourProvider colourProvider) RelativeSizeAxes = Axes.Both, Height = 0.4f }, - new BreakPart - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Height = 0.15f - }, new MarkerPart { RelativeSizeAxes = Axes.Both }, }; } From 73f2f5cb1268f39ca91a729050ba248c8c62689e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 17:59:55 +0900 Subject: [PATCH 334/579] Fix more tests --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 ++ osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 16b2a54a4546..91f22a291c43 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -174,6 +174,7 @@ public void TestHoldForMenuDoesWorkWhenHidden() holdForMenu.Action += () => activated = true; }); + AddStep("set hold button always visible", () => localConfig.SetValue(OsuSetting.AlwaysShowHoldForMenuButton, true)); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); @@ -214,6 +215,7 @@ public void TestInputDoesntWorkWhenHUDHidden() progress.ChildrenOfType().Single().OnSeek += _ => seeked = true; }); + AddStep("set hold button always visible", () => localConfig.SetValue(OsuSetting.AlwaysShowHoldForMenuButton, true)); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 030f2592ed19..6aa2c4e40dac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -320,6 +320,8 @@ public void TestQuickExitFromGameplay() [Test] public void TestExitViaHoldToExit() { + AddStep("set hold button always visible", () => LocalConfig.SetValue(OsuSetting.AlwaysShowHoldForMenuButton, true)); + AddStep("exit", () => { InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.First(c => c is HoldToConfirmContainer)); From a33294ac42717717c5fd603bea2d92fdca18ed50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 11:14:42 +0200 Subject: [PATCH 335/579] Redesign timing table tracking - On entering the screen, the timing point active at the current instant of the map is selected. This is the *only* time where the selected point is changed automatically for the user. - The ongoing automatic tracking of the relevant point after the initial selection is *gone*. Even knowing the fact that it was supposed to track the supposedly relevant "last selected type" of control point, I always found the tracking to be fairly arbitrary in how it works. Removing this behaviour also incidentally fixes https://github.com/ppy/osu/issues/23147. In its stead, to indicate which timing groups are having an effect, they receive an indicator line on the left (coloured using the relevant control points' representing colours), as well as a slight highlight effect. - If there is no control point selected, the table will autoscroll to the latest timing group, unless the user manually scrolled the table before. - If the selected control point changes, the table will autoscroll to the newly selected point, *regardless* of whether the user manually scrolled the table before. - A new button is added which permits the user to select the latest timing group. As per the point above, this will autoscroll the user to that group at the same time. --- .../Screens/Edit/Timing/ControlPointList.cs | 83 +++--------- .../Screens/Edit/Timing/ControlPointTable.cs | 126 ++++++++++++++---- 2 files changed, 117 insertions(+), 92 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index b7367dddda4a..4df52a0a3aca 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Timing @@ -31,7 +30,7 @@ public partial class ControlPointList : CompositeDrawable private Bindable selectedGroup { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) + private void load() { RelativeSizeAxes = Axes.Both; @@ -68,6 +67,14 @@ private void load(OverlayColourProvider colours) Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }, + new RoundedButton + { + Text = "Go to current time", + Action = goToCurrentGroup, + Size = new Vector2(140, 30), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, } }, }; @@ -97,78 +104,18 @@ protected override void Update() { base.Update(); - trackActivePoint(); - addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time; } - private Type? trackedType; - - /// - /// Given the user has selected a control point group, we want to track any group which is - /// active at the current point in time which matches the type the user has selected. - /// - /// So if the user is currently looking at a timing point and seeks into the future, a - /// future timing point would be automatically selected if it is now the new "current" point. - /// - private void trackActivePoint() + private void goToCurrentGroup() { - // For simplicity only match on the first type of the active control point. - if (selectedGroup.Value == null) - trackedType = null; - else - { - switch (selectedGroup.Value.ControlPoints.Count) - { - // If the selected group has no control points, clear the tracked type. - // Otherwise the user will be unable to select a group with no control points. - case 0: - trackedType = null; - break; - - // If the selected group only has one control point, update the tracking type. - case 1: - trackedType = selectedGroup.Value?.ControlPoints[0].GetType(); - break; - - // If the selected group has more than one control point, choose the first as the tracking type - // if we don't already have a singular tracked type. - default: - trackedType ??= selectedGroup.Value?.ControlPoints[0].GetType(); - break; - } - } + double accurateTime = clock.CurrentTimeAccurate; - if (trackedType != null) - { - double accurateTime = clock.CurrentTimeAccurate; - - // We don't have an efficient way of looking up groups currently, only individual point types. - // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. - - // Find the next group which has the same type as the selected one. - ControlPointGroup? found = null; - - for (int i = 0; i < Beatmap.ControlPointInfo.Groups.Count; i++) - { - var g = Beatmap.ControlPointInfo.Groups[i]; - - if (g.Time > accurateTime) - continue; + var activeTimingPoint = Beatmap.ControlPointInfo.TimingPointAt(accurateTime); + var activeEffectPoint = Beatmap.ControlPointInfo.EffectPointAt(accurateTime); - for (int j = 0; j < g.ControlPoints.Count; j++) - { - if (g.ControlPoints[j].GetType() == trackedType) - { - found = g; - break; - } - } - } - - if (found != null) - selectedGroup.Value = found; - } + double latestActiveTime = Math.Max(activeTimingPoint.Time, activeEffectPoint.Time); + selectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(latestActiveTime); } private void delete() diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 8dc0ced30e42..501d8c0e4150 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; @@ -27,10 +28,27 @@ public partial class ControlPointTable : CompositeDrawable { public BindableList Groups { get; } = new BindableList(); + [Cached] + private Bindable activeTimingPoint { get; } = new Bindable(); + + [Cached] + private Bindable activeEffectPoint { get; } = new Bindable(); + + [Resolved] + private EditorBeatmap beatmap { get; set; } = null!; + + [Resolved] + private Bindable selectedGroup { get; set; } = null!; + + [Resolved] + private EditorClock editorClock { get; set; } = null!; + private const float timing_column_width = 300; private const float row_height = 25; private const float row_horizontal_padding = 20; + private ControlPointRowList list = null!; + [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { @@ -65,7 +83,7 @@ private void load(OverlayColourProvider colours) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = ControlPointTable.timing_column_width } + Margin = new MarginPadding { Left = timing_column_width } }, } }, @@ -73,7 +91,7 @@ private void load(OverlayColourProvider colours) { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = row_height }, - Child = new ControlPointRowList + Child = list = new ControlPointRowList { RelativeSizeAxes = Axes.Both, RowData = { BindTarget = Groups, }, @@ -82,40 +100,63 @@ private void load(OverlayColourProvider colours) }; } - private partial class ControlPointRowList : VirtualisedListContainer + protected override void LoadComplete() { - [Resolved] - private Bindable selectedGroup { get; set; } = null!; + base.LoadComplete(); + + selectedGroup.BindValueChanged(_ => scrollToMostRelevantRow(force: true), true); + } + protected override void Update() + { + base.Update(); + + scrollToMostRelevantRow(force: false); + } + + private void scrollToMostRelevantRow(bool force) + { + double accurateTime = editorClock.CurrentTimeAccurate; + + activeTimingPoint.Value = beatmap.ControlPointInfo.TimingPointAt(accurateTime); + activeEffectPoint.Value = beatmap.ControlPointInfo.EffectPointAt(accurateTime); + + double latestActiveTime = Math.Max(activeTimingPoint.Value?.Time ?? double.NegativeInfinity, activeEffectPoint.Value?.Time ?? double.NegativeInfinity); + var groupToShow = selectedGroup.Value ?? beatmap.ControlPointInfo.GroupAt(latestActiveTime); + list.ScrollTo(groupToShow, force); + } + + private partial class ControlPointRowList : VirtualisedListContainer + { public ControlPointRowList() : base(row_height, 50) { } - protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + protected override ScrollContainer CreateScrollContainer() => new UserTrackingScrollContainer(); - protected override void LoadComplete() + protected new UserTrackingScrollContainer Scroll => (UserTrackingScrollContainer)base.Scroll; + + public void ScrollTo(ControlPointGroup group, bool force) { - base.LoadComplete(); + if (Scroll.UserScrolling && !force) + return; - selectedGroup.BindValueChanged(val => - { - // can't use `.ScrollIntoView()` here because of the list virtualisation not giving - // child items valid coordinates from the start, so ballpark something similar - // using estimated row height. - var row = Items.FlowingChildren.SingleOrDefault(item => item.Row.Equals(val.NewValue)); + // can't use `.ScrollIntoView()` here because of the list virtualisation not giving + // child items valid coordinates from the start, so ballpark something similar + // using estimated row height. + var row = Items.FlowingChildren.SingleOrDefault(item => item.Row.Equals(group)); - if (row == null) - return; + if (row == null) + return; - float minPos = row.Y; - float maxPos = minPos + row_height; + float minPos = row.Y; + float maxPos = minPos + row_height; - if (minPos < Scroll.Current) - Scroll.ScrollTo(minPos); - else if (maxPos > Scroll.Current + Scroll.DisplayableContent) - Scroll.ScrollTo(maxPos - Scroll.DisplayableContent); - }); + if (minPos < Scroll.Current) + Scroll.ScrollTo(minPos); + else if (maxPos > Scroll.Current + Scroll.DisplayableContent) + Scroll.ScrollTo(maxPos - Scroll.DisplayableContent); } } @@ -130,13 +171,23 @@ public Bindable Current private readonly BindableWithCurrent current = new BindableWithCurrent(); private Box background = null!; + private Box currentIndicator = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + [Resolved] + private OsuColour colours { get; set; } = null!; + [Resolved] private Bindable selectedGroup { get; set; } = null!; + [Resolved] + private Bindable activeTimingPoint { get; set; } = null!; + + [Resolved] + private Bindable activeEffectPoint { get; set; } = null!; + [Resolved] private EditorClock editorClock { get; set; } = null!; @@ -153,6 +204,12 @@ private void load() Colour = colourProvider.Background1, Alpha = 0, }, + currentIndicator = new Box + { + RelativeSizeAxes = Axes.Y, + Width = 5, + Alpha = 0, + }, new Container { RelativeSizeAxes = Axes.Both, @@ -174,7 +231,9 @@ protected override void LoadComplete() { base.LoadComplete(); - selectedGroup.BindValueChanged(_ => updateState(), true); + selectedGroup.BindValueChanged(_ => updateState()); + activeEffectPoint.BindValueChanged(_ => updateState()); + activeTimingPoint.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } @@ -213,12 +272,31 @@ private void updateState() { bool isSelected = selectedGroup.Value?.Equals(current.Value) == true; + bool hasCurrentTimingPoint = activeTimingPoint.Value != null && current.Value.ControlPoints.Contains(activeTimingPoint.Value); + bool hasCurrentEffectPoint = activeEffectPoint.Value != null && current.Value.ControlPoints.Contains(activeEffectPoint.Value); + if (IsHovered || isSelected) background.FadeIn(100, Easing.OutQuint); + else if (hasCurrentTimingPoint || hasCurrentEffectPoint) + background.FadeTo(0.2f, 100, Easing.OutQuint); else background.FadeOut(100, Easing.OutQuint); background.Colour = isSelected ? colourProvider.Colour3 : colourProvider.Background1; + + if (hasCurrentTimingPoint || hasCurrentEffectPoint) + { + currentIndicator.FadeIn(100, Easing.OutQuint); + + if (hasCurrentTimingPoint && hasCurrentEffectPoint) + currentIndicator.Colour = ColourInfo.GradientVertical(activeTimingPoint.Value!.GetRepresentingColour(colours), activeEffectPoint.Value!.GetRepresentingColour(colours)); + else if (hasCurrentTimingPoint) + currentIndicator.Colour = activeTimingPoint.Value!.GetRepresentingColour(colours); + else + currentIndicator.Colour = activeEffectPoint.Value!.GetRepresentingColour(colours); + } + else + currentIndicator.FadeOut(100, Easing.OutQuint); } } From 333e5b8cac7aa19afac0014732325390dbcdb323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 11:23:39 +0200 Subject: [PATCH 336/579] Remove outdated tests --- .../Visual/Editing/TestSceneTimingScreen.cs | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 6181024230e5..cf07ce24315a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -114,40 +114,6 @@ public void TestSelectedRetainedOverUndo() }); } - [Test] - public void TestTrackingCurrentTimeWhileRunning() - { - AddStep("Select first effect point", () => - { - InputManager.MoveMouseTo(Child.ChildrenOfType().First()); - InputManager.Click(MouseButton.Left); - }); - - AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670); - AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 54670); - - AddStep("Seek to just before next point", () => EditorClock.Seek(69000)); - AddStep("Start clock", () => EditorClock.Start()); - - AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670); - } - - [Test] - public void TestTrackingCurrentTimeWhilePaused() - { - AddStep("Select first effect point", () => - { - InputManager.MoveMouseTo(Child.ChildrenOfType().First()); - InputManager.Click(MouseButton.Left); - }); - - AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670); - AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 54670); - - AddStep("Seek to later", () => EditorClock.Seek(80000)); - AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670); - } - [Test] public void TestScrollControlGroupIntoView() { From 3202c77279b305c268eaed0d857fac252bae1ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 12:36:05 +0200 Subject: [PATCH 337/579] Add failing test --- .../TestSceneHitObjectSampleAdjustments.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 75a68237c87d..65eec740f0ea 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -548,6 +548,63 @@ public void TestHotkeysAffectNodeSamples() hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); } + [Test] + public void TestHotkeysUnifySliderSamplesAndNodeSamples() + { + AddStep("add slider", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new Slider + { + Position = new Vector2(256, 256), + StartTime = 1000, + Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), + Samples = + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_DRUM), + }, + NodeSamples = new List> + { + new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM), + new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM), + }, + new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT), + }, + } + }); + }); + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + + AddStep("set soft bank", () => + { + InputManager.PressKey(Key.LShift); + InputManager.Key(Key.E); + InputManager.ReleaseKey(Key.LShift); + }); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); + hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + AddStep("unify whistle addition", () => InputManager.Key(Key.W)); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + } + [Test] public void TestSelectingObjectDoesNotMutateSamples() { From c9f1ef536136c6a639c38538d6f29b5414bf95d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 12:36:13 +0200 Subject: [PATCH 338/579] Fix incorrect bank set / sample addition logic Closes https://github.com/ppy/osu/issues/29361. Typical case of a few early-returns gone wrong leading to `NodeSamples` not being checked correctly. --- .../Edit/Compose/Components/EditorSelectionHandler.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a4efe66bf8c5..472b48425faa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -229,7 +229,7 @@ bool hasRelevantBank(HitObject hitObject) EditorBeatmap.PerformOnSelection(h => { - if (h.Samples.All(s => s.Bank == bankName)) + if (hasRelevantBank(h)) return; h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList(); @@ -269,10 +269,8 @@ public void AddHitSample(string sampleName) EditorBeatmap.PerformOnSelection(h => { // Make sure there isn't already an existing sample - if (h.Samples.Any(s => s.Name == sampleName)) - return; - - h.Samples.Add(h.CreateHitSampleInfo(sampleName)); + if (h.Samples.All(s => s.Name != sampleName)) + h.Samples.Add(h.CreateHitSampleInfo(sampleName)); if (h is IHasRepeats hasRepeats) { From bb964e32fa5a1e5f2aeb1b3f14308f9c85be02ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 13:36:52 +0200 Subject: [PATCH 339/579] Fix crash on attempting to edit particular beatmaps Closes https://github.com/ppy/osu/issues/29492. I'm not immediately sure why this happened, but some old locally modified beatmaps in my local realm database have a `BeatDivisor` of 0 stored, which is then passed to `BindableBeatDivisor.SetArbitraryDivisor()`, which then blows up. To stop this from happening, just refuse to use values outside of a sane range. --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 10 +++++++++- .../Edit/Compose/Components/BeatDivisorControl.cs | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 4b0726658fe4..3bb1b4e0797d 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -16,6 +16,9 @@ public class BindableBeatDivisor : BindableInt { public static readonly int[] PREDEFINED_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 }; + public const int MINIMUM_DIVISOR = 1; + public const int MAXIMUM_DIVISOR = 64; + public Bindable ValidDivisors { get; } = new Bindable(BeatDivisorPresetCollection.COMMON); public BindableBeatDivisor(int value = 1) @@ -30,8 +33,12 @@ public BindableBeatDivisor(int value = 1) /// /// The intended divisor. /// Forces changing the valid divisors to a known preset. - public void SetArbitraryDivisor(int divisor, bool preferKnownPresets = false) + /// Whether the divisor was successfully set. + public bool SetArbitraryDivisor(int divisor, bool preferKnownPresets = false) { + if (divisor < MINIMUM_DIVISOR || divisor > MAXIMUM_DIVISOR) + return false; + // If the current valid divisor range doesn't contain the proposed value, attempt to find one which does. if (preferKnownPresets || !ValidDivisors.Value.Presets.Contains(divisor)) { @@ -44,6 +51,7 @@ public void SetArbitraryDivisor(int divisor, bool preferKnownPresets = false) } Value = divisor; + return true; } private void updateBindableProperties() diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 1d8266d610e8..3c2a66b8bbc9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -330,14 +330,14 @@ protected override void LoadComplete() private void setPresetsFromTextBoxEntry() { - if (!int.TryParse(divisorTextBox.Text, out int divisor) || divisor < 1 || divisor > 64) + if (!int.TryParse(divisorTextBox.Text, out int divisor) || !BeatDivisor.SetArbitraryDivisor(divisor)) { + // the text either didn't parse as a divisor, or the divisor was not set due to being out of range. + // force a state update to reset the text box's value to the last sane value. updateState(); return; } - BeatDivisor.SetArbitraryDivisor(divisor); - this.HidePopover(); } From c2dd2ad9783412d61a819805a42f1fa4a9dfd12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 13:40:57 +0200 Subject: [PATCH 340/579] Clamp beat divisor to sane range when decoding In my view this is a nice change, but do note that on its own it does nothing to fix https://github.com/ppy/osu/issues/29492, because of `BeatmapInfo` reference management foibles when opening the editor. See also: https://github.com/ppy/osu/issues/20883#issuecomment-1288149271, https://github.com/ppy/osu/pull/28473. --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9418a389aaec..b068c87fbb10 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps.Formats { @@ -336,7 +337,7 @@ private void handleEditor(string line) break; case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = Parsing.ParseInt(pair.Value); + beatmap.BeatmapInfo.BeatDivisor = Math.Clamp(Parsing.ParseInt(pair.Value), BindableBeatDivisor.MINIMUM_DIVISOR, BindableBeatDivisor.MAXIMUM_DIVISOR); break; case @"GridSize": From 2011d5525f7aab8fa1809d16f8801dffaa507f51 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 22:21:10 +0900 Subject: [PATCH 341/579] Add flaky test attribute to some tests See occurences like https://github.com/ppy/osu/actions/runs/10471058714. --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 5a71369976a2..5af7540f6f02 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -27,6 +27,7 @@ public partial class TestSceneLeadIn : RateAdjustedBeatmapTestScene [TestCase(2000, 0)] [TestCase(3000, first_hit_object - 3000)] [TestCase(10000, first_hit_object - 10000)] + [FlakyTest] public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedStartTime) { loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) @@ -41,6 +42,7 @@ public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedSta [TestCase(0, 0)] [TestCase(-1000, -1000)] [TestCase(-10000, -10000)] + [FlakyTest] public void TestStoryboardProducesCorrectStartTimeSimpleAlpha(double firstStoryboardEvent, double expectedStartTime) { var storyboard = new Storyboard(); @@ -64,6 +66,7 @@ public void TestStoryboardProducesCorrectStartTimeSimpleAlpha(double firstStoryb [TestCase(0, 0, true)] [TestCase(-1000, -1000, true)] [TestCase(-10000, -10000, true)] + [FlakyTest] public void TestStoryboardProducesCorrectStartTimeFadeInAfterOtherEvents(double firstStoryboardEvent, double expectedStartTime, bool addEventToLoop) { const double loop_start_time = -20000; From 8e273709f12b58af01d7b6711ba7be11f17010c9 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Tue, 20 Aug 2024 22:48:11 +0800 Subject: [PATCH 342/579] Implement copy url in beatmap and beatmap set carousel --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 9 ++++++++- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index f725d98342cb..70c82576cce6 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Collections; @@ -25,6 +26,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; @@ -53,6 +55,7 @@ public partial class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContext private Action? selectRequested; private Action? hideRequested; + private Action? copyBeatmapSetUrl; private Triangles triangles = null!; @@ -89,7 +92,7 @@ public DrawableCarouselBeatmap(CarouselBeatmap panel) } [BackgroundDependencyLoader] - private void load(BeatmapManager? manager, SongSelect? songSelect) + private void load(BeatmapManager? manager, SongSelect? songSelect, Clipboard clipboard, IAPIProvider api) { Header.Height = height; @@ -102,6 +105,8 @@ private void load(BeatmapManager? manager, SongSelect? songSelect) if (manager != null) hideRequested = manager.Hide; + copyBeatmapSetUrl += () => clipboard.SetText($@"{api.WebsiteRootUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}"); + Header.Children = new Drawable[] { background = new Box @@ -288,6 +293,8 @@ public MenuItem[] ContextMenuItems items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyBeatmapSetUrl?.Invoke())); + if (hideRequested != null) items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index bd659d742385..12db8f663a4f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -8,18 +8,22 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Platform; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Rulesets; namespace osu.Game.Screens.Select.Carousel { @@ -29,6 +33,7 @@ public partial class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasCont private Action restoreHiddenRequested = null!; private Action? viewDetails; + private Action? copyBeatmapSetUrl; [Resolved] private IDialogOverlay? dialogOverlay { get; set; } @@ -65,7 +70,7 @@ protected override void FreeAfterUse() } [BackgroundDependencyLoader] - private void load(BeatmapSetOverlay? beatmapOverlay, SongSelect? songSelect) + private void load(BeatmapSetOverlay? beatmapOverlay, SongSelect? songSelect, Clipboard clipboard, IBindable ruleset, IAPIProvider api) { if (songSelect != null) mainMenuItems = songSelect.CreateForwardNavigationMenuItemsForBeatmap(() => (((CarouselBeatmapSet)Item!).GetNextToSelect() as CarouselBeatmap)!.BeatmapInfo); @@ -78,6 +83,8 @@ private void load(BeatmapSetOverlay? beatmapOverlay, SongSelect? songSelect) if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; + + copyBeatmapSetUrl += () => clipboard.SetText($@"{api.WebsiteRootUrl}/beatmapsets/{beatmapSet.OnlineID}#{ruleset.Value.ShortName}"); } protected override void Update() @@ -287,6 +294,8 @@ public MenuItem[] ContextMenuItems if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyBeatmapSetUrl?.Invoke())); + if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); return items.ToArray(); From 20658ef4eeebbf3d09515c777305ed145a9646b3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Aug 2024 00:02:05 +0900 Subject: [PATCH 343/579] Fix legacy key counter position not matching stable --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 6 ++---- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 81279456d57a..f3626eb55de0 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -56,10 +56,8 @@ public CatchLegacySkinTransformer(ISkin skin) { // set the anchor to top right so that it won't squash to the return button to the top keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + keyCounter.Origin = Anchor.TopRight; + keyCounter.Position = new Vector2(0, -40) * 1.6f; } }) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 491eb02e2619..457c191583d4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -69,10 +69,8 @@ public OsuLegacySkinTransformer(ISkin skin) { // set the anchor to top right so that it won't squash to the return button to the top keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + keyCounter.Origin = Anchor.TopRight; + keyCounter.Position = new Vector2(0, -40) * 1.6f; } var combo = container.OfType().FirstOrDefault(); From 0d358a1dae593b83cf8e871b838de09880f848e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Aug 2024 02:53:11 +0900 Subject: [PATCH 344/579] Fix resume overlay appearing behind HUD/skip overlays --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9a3d83782f59..f362373b2425 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -442,7 +442,6 @@ private Drawable createOverlayComponents(IWorkingBeatmap working) }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), - DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods, Configuration.AlwaysShowLeaderboard) { HoldToQuit = @@ -470,6 +469,7 @@ private Drawable createOverlayComponents(IWorkingBeatmap working) RequestSkip = () => progressToResults(false), Alpha = 0 }, + DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), PauseOverlay = new PauseOverlay { OnResume = Resume, From ae4fefeba15d0a64371d6def3da9ced22c65d607 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Aug 2024 03:22:03 +0900 Subject: [PATCH 345/579] Add failing test case --- .../TestSceneModCustomisationPanel.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index c2739e1bbdf8..0d8ea0561200 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -7,6 +7,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; @@ -157,6 +159,27 @@ public void TestHoverExpandsAndCollapsesWhenHeaderClicked() checkExpanded(false); } + [Test] + public void TestDraggingKeepsPanelExpanded() + { + AddStep("add customisable mod", () => + { + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + panel.Enabled.Value = true; + }); + + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); + + AddStep("hover slider bar nub", () => InputManager.MoveMouseTo(panel.ChildrenOfType>().First().ChildrenOfType().Single())); + AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); + AddStep("drag outside", () => InputManager.MoveMouseTo(Vector2.Zero)); + checkExpanded(true); + + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + checkExpanded(false); + } + private void checkExpanded(bool expanded) { AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.ExpandedState.Value, From b7599dd1f830d5b5c617c025ba8b86893e368da5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Aug 2024 03:23:23 +0900 Subject: [PATCH 346/579] Keep mod customisation panel open when dragging a drawable --- .../Overlays/Mods/ModCustomisationPanel.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 75cd5d6c91ea..91d7fdda7378 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -214,15 +215,23 @@ public FocusGrabbingContainer(ModCustomisationPanel panel) this.panel = panel; } - protected override void OnHoverLost(HoverLostEvent e) + private InputManager? inputManager; + + protected override void LoadComplete() + { + base.LoadComplete(); + inputManager = GetContainingInputManager(); + } + + protected override void Update() { - if (ExpandedState.Value is ModCustomisationPanelState.ExpandedByHover - && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + base.Update(); + + if (ExpandedState.Value == ModCustomisationPanelState.ExpandedByHover) { - ExpandedState.Value = ModCustomisationPanelState.Collapsed; + if (!ReceivePositionalInputAt(inputManager!.CurrentState.Mouse.Position) && inputManager.DraggedDrawable == null) + ExpandedState.Value = ModCustomisationPanelState.Collapsed; } - - base.OnHoverLost(e); } } From 637c9aeef0c4879c3ad8e5adbf8b7fcfe9c21472 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Aug 2024 03:35:26 +0900 Subject: [PATCH 347/579] Add `DailyChallengeIntroPlayed` session static --- osu.Game/Configuration/SessionStatics.cs | 6 ++++++ osu.Game/Screens/Menu/DailyChallengeButton.cs | 7 +++++++ osu.Game/Screens/Menu/MainMenu.cs | 5 ++++- .../OnlinePlay/DailyChallenge/DailyChallengeIntro.cs | 5 +++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 1548b781a704..225f20938039 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -80,5 +80,11 @@ public enum Static /// Stores the local user's last score (can be completed or aborted). /// LastLocalUserScore, + + /// + /// Whether the intro animation for the daily challenge screen has been played once. + /// This is reset when a new challenge is up. + /// + DailyChallengeIntroPlayed, } } diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index e6593c9b0d5f..d47866ef7334 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -13,6 +13,7 @@ using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps.Drawables; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; @@ -46,6 +47,9 @@ public partial class DailyChallengeButton : MainMenuButton [Resolved] private INotificationOverlay? notificationOverlay { get; set; } + [Resolved] + private SessionStatics statics { get; set; } = null!; + public DailyChallengeButton(string sampleName, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) : base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys) { @@ -148,6 +152,9 @@ private void dailyChallengeChanged(ValueChangedEvent _) roomRequest.Success += room => { + // force showing intro on the first time when a new daily challenge is up. + statics.SetValue(Static.DailyChallengeIntroPlayed, false); + Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dfe5460aee96..64a173e08854 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -150,7 +150,10 @@ private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings OnPlaylists = () => this.Push(new Playlists()), OnDailyChallenge = room => { - this.Push(new DailyChallengeIntro(room)); + if (statics.Get(Static.DailyChallengeIntroPlayed)) + this.Push(new DailyChallenge(room)); + else + this.Push(new DailyChallengeIntro(room)); }, OnExit = () => { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index e59031f66355..619e7c1e4224 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -70,6 +70,9 @@ public partial class DailyChallengeIntro : OsuScreen [Resolved] private MusicController musicController { get; set; } = null!; + [Resolved] + private SessionStatics statics { get; set; } = null!; + private Sample? dateWindupSample; private Sample? dateImpactSample; private Sample? beatmapWindupSample; @@ -461,6 +464,8 @@ private void beginAnimation() { Schedule(() => { + statics.SetValue(Static.DailyChallengeIntroPlayed, true); + if (this.IsCurrentScreen()) this.Push(new DailyChallenge(room)); }); From 1ce9e97fd45bb81f13a8e6a799af43d6342922af Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 20 Aug 2024 23:38:38 +0200 Subject: [PATCH 348/579] add arrow indicator --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6cd70449432a..9c42d072d128 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Audio; @@ -165,6 +166,13 @@ public partial class SampleEditPopover : OsuPopover [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } = null!; + protected override Drawable CreateArrow() => new Triangle + { + Size = new Vector2(20), + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + }; + public SampleEditPopover(HitObject hitObject) { this.hitObject = hitObject; From db568bfb79d5d037e8a0e9d1485917421483da9a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 01:01:17 +0200 Subject: [PATCH 349/579] add tests --- .../Editor/TestSceneSliderChangeStates.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderChangeStates.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderChangeStates.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderChangeStates.cs new file mode 100644 index 000000000000..0b8f2f741760 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderChangeStates.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public partial class TestSceneSliderChangeStates : TestSceneOsuEditor + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [TestCase(SplineType.Catmull)] + [TestCase(SplineType.BSpline)] + [TestCase(SplineType.Linear)] + [TestCase(SplineType.PerfectCurve)] + public void TestSliderRetainsCurveTypes(SplineType splineType) + { + Slider? slider = null; + PathType pathType = new PathType(splineType); + + AddStep("add slider", () => EditorBeatmap.Add(slider = new Slider + { + StartTime = 500, + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero, pathType), + new PathControlPoint(new Vector2(200, 0), pathType), + }) + })); + AddAssert("slider has correct spline type", () => ((Slider)EditorBeatmap.HitObjects[0]).Path.ControlPoints.All(p => p.Type == pathType)); + AddStep("remove object", () => EditorBeatmap.Remove(slider)); + AddAssert("slider removed", () => EditorBeatmap.HitObjects.Count == 0); + addUndoSteps(); + AddAssert("slider not removed", () => EditorBeatmap.HitObjects.Count == 1); + AddAssert("slider has correct spline type", () => ((Slider)EditorBeatmap.HitObjects[0]).Path.ControlPoints.All(p => p.Type == pathType)); + } + + private void addUndoSteps() => AddStep("undo", () => Editor.Undo()); + } +} From 8d72ec8bd6977676a56dd4bacb7e53a2190f0469 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 01:50:52 +0200 Subject: [PATCH 350/579] move timing point binary search back inline --- .../NonVisual/ControlPointInfoTest.cs | 58 +++++++++++ osu.Game.Tests/Utils/BinarySearchUtilsTest.cs | 66 ------------- .../ControlPoints/ControlPointInfo.cs | 80 ++++++++++++++- osu.Game/Utils/BinarySearchUtils.cs | 98 ------------------- 4 files changed, 136 insertions(+), 166 deletions(-) delete mode 100644 osu.Game.Tests/Utils/BinarySearchUtilsTest.cs delete mode 100644 osu.Game/Utils/BinarySearchUtils.cs diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index 2d5d425ee8f9..d7df3d318db9 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps.ControlPoints; @@ -286,5 +287,62 @@ public void TestDeepClone() Assert.That(cpi.TimingPoints[0].BeatLength, Is.Not.EqualTo(cpiCopy.TimingPoints[0].BeatLength)); } + + [Test] + public void TestBinarySearchEmptyList() + { + Assert.That(ControlPointInfo.BinarySearch(Array.Empty(), 0, EqualitySelection.FirstFound), Is.EqualTo(-1)); + Assert.That(ControlPointInfo.BinarySearch(Array.Empty(), 0, EqualitySelection.Leftmost), Is.EqualTo(-1)); + Assert.That(ControlPointInfo.BinarySearch(Array.Empty(), 0, EqualitySelection.Rightmost), Is.EqualTo(-1)); + } + + [TestCase(new[] { 1 }, 0, -1)] + [TestCase(new[] { 1 }, 1, 0)] + [TestCase(new[] { 1 }, 2, -2)] + [TestCase(new[] { 1, 3 }, 0, -1)] + [TestCase(new[] { 1, 3 }, 1, 0)] + [TestCase(new[] { 1, 3 }, 2, -2)] + [TestCase(new[] { 1, 3 }, 3, 1)] + [TestCase(new[] { 1, 3 }, 4, -3)] + public void TestBinarySearchUniqueScenarios(int[] values, int search, int expectedIndex) + { + var items = values.Select(t => new TimingControlPoint { Time = t }).ToArray(); + Assert.That(ControlPointInfo.BinarySearch(items, search, EqualitySelection.FirstFound), Is.EqualTo(expectedIndex)); + Assert.That(ControlPointInfo.BinarySearch(items, search, EqualitySelection.Leftmost), Is.EqualTo(expectedIndex)); + Assert.That(ControlPointInfo.BinarySearch(items, search, EqualitySelection.Rightmost), Is.EqualTo(expectedIndex)); + } + + [TestCase(new[] { 1, 1 }, 1, 0)] + [TestCase(new[] { 1, 2, 2 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 2 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 2)] + [TestCase(new[] { 1, 2, 2, 3 }, 2, 1)] + public void TestBinarySearchFirstFoundDuplicateScenarios(int[] values, int search, int expectedIndex) + { + var items = values.Select(t => new TimingControlPoint { Time = t }).ToArray(); + Assert.That(ControlPointInfo.BinarySearch(items, search, EqualitySelection.FirstFound), Is.EqualTo(expectedIndex)); + } + + [TestCase(new[] { 1, 1 }, 1, 0)] + [TestCase(new[] { 1, 2, 2 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 2 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 1)] + [TestCase(new[] { 1, 2, 2, 3 }, 2, 1)] + public void TestBinarySearchLeftMostDuplicateScenarios(int[] values, int search, int expectedIndex) + { + var items = values.Select(t => new TimingControlPoint { Time = t }).ToArray(); + Assert.That(ControlPointInfo.BinarySearch(items, search, EqualitySelection.Leftmost), Is.EqualTo(expectedIndex)); + } + + [TestCase(new[] { 1, 1 }, 1, 1)] + [TestCase(new[] { 1, 2, 2 }, 2, 2)] + [TestCase(new[] { 1, 2, 2, 2 }, 2, 3)] + [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 3)] + [TestCase(new[] { 1, 2, 2, 3 }, 2, 2)] + public void TestBinarySearchRightMostDuplicateScenarios(int[] values, int search, int expectedIndex) + { + var items = values.Select(t => new TimingControlPoint { Time = t }).ToArray(); + Assert.That(ControlPointInfo.BinarySearch(items, search, EqualitySelection.Rightmost), Is.EqualTo(expectedIndex)); + } } } diff --git a/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs b/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs deleted file mode 100644 index cbf6cdf32ae6..000000000000 --- a/osu.Game.Tests/Utils/BinarySearchUtilsTest.cs +++ /dev/null @@ -1,66 +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 System; -using NUnit.Framework; -using osu.Game.Utils; - -namespace osu.Game.Tests.Utils -{ - [TestFixture] - public class BinarySearchUtilsTest - { - [Test] - public void TestEmptyList() - { - Assert.That(BinarySearchUtils.BinarySearch(Array.Empty(), 0, x => x), Is.EqualTo(-1)); - Assert.That(BinarySearchUtils.BinarySearch(Array.Empty(), 0, x => x, EqualitySelection.Leftmost), Is.EqualTo(-1)); - Assert.That(BinarySearchUtils.BinarySearch(Array.Empty(), 0, x => x, EqualitySelection.Rightmost), Is.EqualTo(-1)); - } - - [TestCase(new[] { 1 }, 0, -1)] - [TestCase(new[] { 1 }, 1, 0)] - [TestCase(new[] { 1 }, 2, -2)] - [TestCase(new[] { 1, 3 }, 0, -1)] - [TestCase(new[] { 1, 3 }, 1, 0)] - [TestCase(new[] { 1, 3 }, 2, -2)] - [TestCase(new[] { 1, 3 }, 3, 1)] - [TestCase(new[] { 1, 3 }, 4, -3)] - public void TestUniqueScenarios(int[] values, int search, int expectedIndex) - { - Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.FirstFound), Is.EqualTo(expectedIndex)); - Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Leftmost), Is.EqualTo(expectedIndex)); - Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Rightmost), Is.EqualTo(expectedIndex)); - } - - [TestCase(new[] { 1, 1 }, 1, 0)] - [TestCase(new[] { 1, 2, 2 }, 2, 1)] - [TestCase(new[] { 1, 2, 2, 2 }, 2, 1)] - [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 2)] - [TestCase(new[] { 1, 2, 2, 3 }, 2, 1)] - public void TestFirstFoundDuplicateScenarios(int[] values, int search, int expectedIndex) - { - Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x), Is.EqualTo(expectedIndex)); - } - - [TestCase(new[] { 1, 1 }, 1, 0)] - [TestCase(new[] { 1, 2, 2 }, 2, 1)] - [TestCase(new[] { 1, 2, 2, 2 }, 2, 1)] - [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 1)] - [TestCase(new[] { 1, 2, 2, 3 }, 2, 1)] - public void TestLeftMostDuplicateScenarios(int[] values, int search, int expectedIndex) - { - Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Leftmost), Is.EqualTo(expectedIndex)); - } - - [TestCase(new[] { 1, 1 }, 1, 1)] - [TestCase(new[] { 1, 2, 2 }, 2, 2)] - [TestCase(new[] { 1, 2, 2, 2 }, 2, 3)] - [TestCase(new[] { 1, 2, 2, 2, 3 }, 2, 3)] - [TestCase(new[] { 1, 2, 2, 3 }, 2, 2)] - public void TestRightMostDuplicateScenarios(int[] values, int search, int expectedIndex) - { - Assert.That(BinarySearchUtils.BinarySearch(values, search, x => x, EqualitySelection.Rightmost), Is.EqualTo(expectedIndex)); - } - } -} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 026d44faa1c1..8666f0112917 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -82,7 +82,7 @@ public class ControlPointInfo : IDeepCloneable [CanBeNull] public TimingControlPoint TimingPointAfter(double time) { - int index = BinarySearchUtils.BinarySearch(TimingPoints, time, c => c.Time, EqualitySelection.Rightmost); + int index = BinarySearch(TimingPoints, time, EqualitySelection.Rightmost); index = index < 0 ? ~index : index + 1; return index < TimingPoints.Count ? TimingPoints[index] : null; } @@ -250,7 +250,7 @@ public static T BinarySearch(IReadOnlyList list, double time) { ArgumentNullException.ThrowIfNull(list); - int index = BinarySearchUtils.BinarySearch(list, time, c => c.Time, EqualitySelection.Rightmost); + int index = BinarySearch(list, time, EqualitySelection.Rightmost); if (index < 0) index = ~index - 1; @@ -258,6 +258,75 @@ public static T BinarySearch(IReadOnlyList list, double time) return index >= 0 ? list[index] : null; } + /// + /// Binary searches one of the control point lists to find the active control point at . + /// + /// The list to search. + /// The time to find the control point at. + /// Determines which index to return if there are multiple exact matches. + /// The index of the control point at . Will return the complement of the index of the control point after if no exact match is found. + public static int BinarySearch(IReadOnlyList list, double time, EqualitySelection equalitySelection) + where T : class, IControlPoint + { + ArgumentNullException.ThrowIfNull(list); + + int n = list.Count; + + if (n == 0) + return -1; + + if (time < list[0].Time) + return -1; + + if (time > list[^1].Time) + return ~n; + + int l = 0; + int r = n - 1; + bool equalityFound = false; + + while (l <= r) + { + int pivot = l + ((r - l) >> 1); + + if (list[pivot].Time < time) + l = pivot + 1; + else if (list[pivot].Time > time) + r = pivot - 1; + else + { + equalityFound = true; + + switch (equalitySelection) + { + case EqualitySelection.Leftmost: + r = pivot - 1; + break; + + case EqualitySelection.Rightmost: + l = pivot + 1; + break; + + default: + case EqualitySelection.FirstFound: + return pivot; + } + } + } + + if (!equalityFound) return ~l; + + switch (equalitySelection) + { + case EqualitySelection.Leftmost: + return l; + + default: + case EqualitySelection.Rightmost: + return l - 1; + } + } + /// /// Check whether should be added. /// @@ -328,4 +397,11 @@ public ControlPointInfo DeepClone() return controlPointInfo; } } + + public enum EqualitySelection + { + FirstFound, + Leftmost, + Rightmost + } } diff --git a/osu.Game/Utils/BinarySearchUtils.cs b/osu.Game/Utils/BinarySearchUtils.cs deleted file mode 100644 index 08ce4e363d4c..000000000000 --- a/osu.Game/Utils/BinarySearchUtils.cs +++ /dev/null @@ -1,98 +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 System; -using System.Collections.Generic; - -namespace osu.Game.Utils -{ - public class BinarySearchUtils - { - /// - /// Finds the index of the item in the sorted list which has its property equal to the search term. - /// If no exact match is found, the complement of the index of the first item greater than the search term will be returned. - /// - /// The type of the items in the list to search. - /// The type of the property to perform the search on. - /// The list of items to search. - /// The query to find. - /// Function that maps an item in the list to its index property. - /// Determines which index to return if there are multiple exact matches. - /// The index of the found item. Will return the complement of the index of the first item greater than the search query if no exact match is found. - public static int BinarySearch(IReadOnlyList list, T2 searchTerm, Func termFunc, EqualitySelection equalitySelection = EqualitySelection.FirstFound) - { - int n = list.Count; - - if (n == 0) - return -1; - - var comparer = Comparer.Default; - - if (comparer.Compare(searchTerm, termFunc(list[0])) == -1) - return -1; - - if (comparer.Compare(searchTerm, termFunc(list[^1])) == 1) - return ~n; - - int min = 0; - int max = n - 1; - bool equalityFound = false; - - while (min <= max) - { - int mid = min + (max - min) / 2; - T2 midTerm = termFunc(list[mid]); - - switch (comparer.Compare(midTerm, searchTerm)) - { - case 0: - equalityFound = true; - - switch (equalitySelection) - { - case EqualitySelection.Leftmost: - max = mid - 1; - break; - - case EqualitySelection.Rightmost: - min = mid + 1; - break; - - default: - case EqualitySelection.FirstFound: - return mid; - } - - break; - - case 1: - max = mid - 1; - break; - - case -1: - min = mid + 1; - break; - } - } - - if (!equalityFound) return ~min; - - switch (equalitySelection) - { - case EqualitySelection.Leftmost: - return min; - - default: - case EqualitySelection.Rightmost: - return min - 1; - } - } - } - - public enum EqualitySelection - { - FirstFound, - Leftmost, - Rightmost - } -} From c4f08b42abacb959008d35535246fae3a0cb801f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Aug 2024 09:05:10 +0200 Subject: [PATCH 351/579] Use colours to distinguish buttons better --- osu.Game/Screens/Edit/Timing/ControlPointList.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 4df52a0a3aca..cbef0b906480 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osuTK; @@ -30,7 +31,7 @@ public partial class ControlPointList : CompositeDrawable private Bindable selectedGroup { get; set; } = null!; [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { RelativeSizeAxes = Axes.Both; @@ -59,6 +60,7 @@ private void load() Action = delete, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, + BackgroundColour = colours.Red3, }, addButton = new RoundedButton { @@ -66,6 +68,7 @@ private void load() Size = new Vector2(160, 30), Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, + BackgroundColour = colours.Green3, }, new RoundedButton { From a0002943a1ac11f653570366710f61ef45cd289c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2024 15:51:02 +0900 Subject: [PATCH 352/579] Adjust centre marker visuals a bit --- .../Components/Timeline/CentreMarker.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 7d8622905cda..5282fbf1fc3d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -14,22 +14,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class CentreMarker : CompositeDrawable { - private const float triangle_width = 8; - - private const float bar_width = 1.6f; - - public CentreMarker() + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colours) { + const float triangle_width = 8; + const float bar_width = 2f; + RelativeSizeAxes = Axes.Y; - Size = new Vector2(triangle_width, 1); Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; - } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) - { + Size = new Vector2(triangle_width, 1); + InternalChildren = new Drawable[] { new Box @@ -47,6 +44,15 @@ private void load(OverlayColourProvider colours) Origin = Anchor.BottomCentre, Size = new Vector2(triangle_width, triangle_width * 0.8f), Scale = new Vector2(1, -1), + EdgeSmoothness = new Vector2(1, 0), + Colour = colours.Colour2, + }, + new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(triangle_width, triangle_width * 0.8f), + Scale = new Vector2(1, 1), Colour = colours.Colour2, }, }; From 3065f808a78761935ca84cc4f4c03882eeed4806 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2024 00:50:41 +0900 Subject: [PATCH 353/579] Simplify timing point display on timeline --- .../Compose/Components/Timeline/Timeline.cs | 7 +- .../Timeline/TimelineControlPointDisplay.cs | 98 ----------- .../Timeline/TimelineControlPointGroup.cs | 52 ------ .../Timeline/TimelineTimingChangeDisplay.cs | 164 ++++++++++++++++++ .../Components/Timeline/TimingPointPiece.cs | 29 ---- .../Components/Timeline/TopPointPiece.cs | 91 ---------- 6 files changed, 168 insertions(+), 273 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs delete mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTimingChangeDisplay.cs delete mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs delete mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 7a28f7bbaae7..af53697b050f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -78,7 +78,7 @@ public bool AlwaysShowControlPoints private TimelineTickDisplay ticks = null!; - private TimelineControlPointDisplay controlPoints = null!; + private TimelineTimingChangeDisplay controlPoints = null!; private Container mainContent = null!; @@ -117,10 +117,11 @@ private void load(IBindable beatmap, OsuColour colours, OsuConfi AddRange(new Drawable[] { - controlPoints = new TimelineControlPointDisplay + ticks = new TimelineTickDisplay(), + controlPoints = new TimelineTimingChangeDisplay { RelativeSizeAxes = Axes.X, - Height = timeline_expanded_height, + Height = timeline_expanded_height - timeline_height, }, ticks, mainContent = new Container diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs deleted file mode 100644 index 116a3ee1050c..000000000000 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Caching; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; - -namespace osu.Game.Screens.Edit.Compose.Components.Timeline -{ - /// - /// The part of the timeline that displays the control points. - /// - public partial class TimelineControlPointDisplay : TimelinePart - { - [Resolved] - private Timeline timeline { get; set; } = null!; - - /// - /// The visible time/position range of the timeline. - /// - private (float min, float max) visibleRange = (float.MinValue, float.MaxValue); - - private readonly Cached groupCache = new Cached(); - - private readonly IBindableList controlPointGroups = new BindableList(); - - protected override void LoadBeatmap(EditorBeatmap beatmap) - { - base.LoadBeatmap(beatmap); - - controlPointGroups.UnbindAll(); - controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((_, _) => groupCache.Invalidate(), true); - } - - protected override void Update() - { - base.Update(); - - if (DrawWidth <= 0) return; - - (float, float) newRange = ( - (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - TopPointPiece.WIDTH) / DrawWidth * Content.RelativeChildSize.X, - (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X) / DrawWidth * Content.RelativeChildSize.X); - - if (visibleRange != newRange) - { - visibleRange = newRange; - groupCache.Invalidate(); - } - - if (!groupCache.IsValid) - { - recreateDrawableGroups(); - groupCache.Validate(); - } - } - - private void recreateDrawableGroups() - { - // Remove groups outside the visible range - foreach (TimelineControlPointGroup drawableGroup in this) - { - if (!shouldBeVisible(drawableGroup.Group)) - drawableGroup.Expire(); - } - - // Add remaining ones - for (int i = 0; i < controlPointGroups.Count; i++) - { - var group = controlPointGroups[i]; - - if (!shouldBeVisible(group)) - continue; - - bool alreadyVisible = false; - - foreach (var g in this) - { - if (ReferenceEquals(g.Group, group)) - { - alreadyVisible = true; - break; - } - } - - if (alreadyVisible) - continue; - - Add(new TimelineControlPointGroup(group)); - } - } - - private bool shouldBeVisible(ControlPointGroup group) => group.Time >= visibleRange.min && group.Time <= visibleRange.max; - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs deleted file mode 100644 index 98556fda4520..000000000000 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Screens.Edit.Compose.Components.Timeline -{ - public partial class TimelineControlPointGroup : CompositeDrawable - { - public readonly ControlPointGroup Group; - - private readonly IBindableList controlPoints = new BindableList(); - - public TimelineControlPointGroup(ControlPointGroup group) - { - Group = group; - - RelativePositionAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - - Origin = Anchor.TopLeft; - - // offset visually to avoid overlapping timeline tick display. - X = (float)group.Time + 6; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - controlPoints.BindTo(Group.ControlPoints); - controlPoints.BindCollectionChanged((_, _) => - { - ClearInternal(); - - foreach (var point in controlPoints) - { - switch (point) - { - case TimingControlPoint timingPoint: - AddInternal(new TimingPointPiece(timingPoint)); - break; - } - } - }, true); - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTimingChangeDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTimingChangeDisplay.cs new file mode 100644 index 000000000000..908aa6bc76f5 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTimingChangeDisplay.cs @@ -0,0 +1,164 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Caching; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + /// + /// The part of the timeline that displays the control points. + /// + public partial class TimelineTimingChangeDisplay : TimelinePart + { + [Resolved] + private Timeline timeline { get; set; } = null!; + + /// + /// The visible time/position range of the timeline. + /// + private (float min, float max) visibleRange = (float.MinValue, float.MaxValue); + + private readonly Cached groupCache = new Cached(); + + private ControlPointInfo controlPointInfo = null!; + + protected override void LoadBeatmap(EditorBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + + beatmap.ControlPointInfo.ControlPointsChanged += () => groupCache.Invalidate(); + controlPointInfo = beatmap.ControlPointInfo; + } + + protected override void Update() + { + base.Update(); + + if (DrawWidth <= 0) return; + + (float, float) newRange = ( + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - TimingPointPiece.WIDTH) / DrawWidth * Content.RelativeChildSize.X, + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + TimingPointPiece.WIDTH) / DrawWidth * Content.RelativeChildSize.X); + + if (visibleRange != newRange) + { + visibleRange = newRange; + groupCache.Invalidate(); + } + + if (!groupCache.IsValid) + { + recreateDrawableGroups(); + groupCache.Validate(); + } + } + + private void recreateDrawableGroups() + { + // Remove groups outside the visible range (or timing points which have since been removed from the beatmap). + foreach (TimingPointPiece drawableGroup in this) + { + if (!controlPointInfo.TimingPoints.Contains(drawableGroup.Point) || !shouldBeVisible(drawableGroup.Point)) + drawableGroup.Expire(); + } + + // Add remaining / new ones. + foreach (TimingControlPoint t in controlPointInfo.TimingPoints) + attemptAddTimingPoint(t); + } + + private void attemptAddTimingPoint(TimingControlPoint point) + { + if (!shouldBeVisible(point)) + return; + + foreach (var child in this) + { + if (ReferenceEquals(child.Point, point)) + return; + } + + Add(new TimingPointPiece(point)); + } + + private bool shouldBeVisible(TimingControlPoint point) => point.Time >= visibleRange.min && point.Time <= visibleRange.max; + + public partial class TimingPointPiece : CompositeDrawable + { + public const float WIDTH = 16; + + public readonly TimingControlPoint Point; + + private readonly BindableNumber beatLength; + + protected OsuSpriteText Label { get; private set; } = null!; + + public TimingPointPiece(TimingControlPoint timingPoint) + { + RelativePositionAxes = Axes.X; + + RelativeSizeAxes = Axes.Y; + Width = WIDTH; + + Origin = Anchor.TopRight; + + Point = timingPoint; + + beatLength = timingPoint.BeatLengthBindable.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + const float corner_radius = PointVisualisation.MAX_WIDTH / 2; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Colour = Point.GetRepresentingColour(colours), + Masking = true, + CornerRadius = corner_radius, + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + }, + Label = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Rotation = 90, + Padding = new MarginPadding { Horizontal = 2 }, + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + } + }; + + beatLength.BindValueChanged(beatLength => + { + Label.Text = $"{60000 / beatLength.NewValue:n1} BPM"; + }, true); + } + + protected override void Update() + { + base.Update(); + X = (float)Point.Time; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs deleted file mode 100644 index 2a4ad6691838..000000000000 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Screens.Edit.Compose.Components.Timeline -{ - public partial class TimingPointPiece : TopPointPiece - { - private readonly BindableNumber beatLength; - - public TimingPointPiece(TimingControlPoint point) - : base(point) - { - beatLength = point.BeatLengthBindable.GetBoundCopy(); - } - - [BackgroundDependencyLoader] - private void load() - { - beatLength.BindValueChanged(beatLength => - { - Label.Text = $"{60000 / beatLength.NewValue:n1} BPM"; - }, true); - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs deleted file mode 100644 index a40a80536158..000000000000 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.Edit.Compose.Components.Timeline -{ - public partial class TopPointPiece : CompositeDrawable - { - protected readonly ControlPoint Point; - - protected OsuSpriteText Label { get; private set; } = null!; - - public const float WIDTH = 80; - - public TopPointPiece(ControlPoint point) - { - Point = point; - Width = WIDTH; - Height = 16; - Margin = new MarginPadding { Vertical = 4 }; - - Origin = Anchor.TopCentre; - Anchor = Anchor.TopCentre; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - const float corner_radius = 4; - const float arrow_extension = 3; - const float triangle_portion = 15; - - InternalChildren = new Drawable[] - { - // This is a triangle, trust me. - // Doing it this way looks okay. Doing it using Triangle primitive is basically impossible. - new Container - { - Colour = Point.GetRepresentingColour(colours), - X = -corner_radius, - Size = new Vector2(triangle_portion * arrow_extension, Height), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Masking = true, - CornerRadius = Height, - CornerExponent = 1.4f, - Children = new Drawable[] - { - new Box - { - Colour = Color4.White, - RelativeSizeAxes = Axes.Both, - }, - } - }, - new Container - { - RelativeSizeAxes = Axes.Y, - Width = WIDTH - triangle_portion, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = Point.GetRepresentingColour(colours), - Masking = true, - CornerRadius = corner_radius, - Child = new Box - { - Colour = Color4.White, - RelativeSizeAxes = Axes.Both, - }, - }, - Label = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Padding = new MarginPadding(3), - Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold), - Colour = colours.B5, - } - }; - } - } -} From 1a48a6f6542404e79cc8787d895e75ab90742ac5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2024 00:44:29 +0900 Subject: [PATCH 354/579] Reduce size of hit objects on timeline --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index a168dcbd3e3f..6c0d5af24769 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class TimelineHitObjectBlueprint : SelectionBlueprint { - private const float circle_size = 38; + private const float circle_size = 32; private Container? repeatsContainer; From 7e6490133d6588582171c1121083021f4ae88075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 22:24:48 +0900 Subject: [PATCH 355/579] Adjust visuals of tick display (and fine tune some other timeline elements) --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 8 ++-- .../Compose/Components/BeatDivisorControl.cs | 2 +- .../Components/Timeline/CentreMarker.cs | 7 +--- .../Compose/Components/Timeline/Timeline.cs | 40 +++++++++---------- .../Timeline/TimelineTickDisplay.cs | 26 +++++++----- .../Timeline/TimelineTimingChangeDisplay.cs | 5 +-- 6 files changed, 42 insertions(+), 46 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 3bb1b4e0797d..bd9c9bab9a88 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -145,18 +145,18 @@ public static Vector2 GetSize(int beatDivisor) { case 1: case 2: - return new Vector2(0.6f, 0.9f); + return new Vector2(1, 0.9f); case 3: case 4: - return new Vector2(0.5f, 0.8f); + return new Vector2(0.8f, 0.8f); case 6: case 8: - return new Vector2(0.4f, 0.7f); + return new Vector2(0.8f, 0.7f); default: - return new Vector2(0.3f, 0.6f); + return new Vector2(0.8f, 0.6f); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 3c2a66b8bbc9..43a2abe4c441 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -526,7 +526,7 @@ public Tick(int divisor, bool alwaysDisplayed) AlwaysDisplayed = alwaysDisplayed; Divisor = divisor; - Size = new Vector2(6f, 18) * BindableBeatDivisor.GetSize(divisor); + Size = new Vector2(4, 18) * BindableBeatDivisor.GetSize(divisor); Alpha = alwaysDisplayed ? 1 : 0; InternalChild = new Box { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 5282fbf1fc3d..c63dfdfb559d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. 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.Shapes; using osu.Game.Overlays; @@ -29,14 +27,13 @@ private void load(OverlayColourProvider colours) InternalChildren = new Drawable[] { - new Box + new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Width = bar_width, - Blending = BlendingParameters.Additive, - Colour = ColourInfo.GradientVertical(colours.Colour2.Opacity(0.6f), colours.Colour2.Opacity(0)), + Colour = colours.Colour2, }, new Triangle { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index af53697b050f..3fa9fc8e3df2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -14,7 +14,9 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; using osuTK; using osuTK.Input; @@ -24,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider { private const float timeline_height = 80; - private const float timeline_expanded_height = 94; + private const float timeline_expanded_height = 80; private readonly Drawable userContent; @@ -103,32 +105,28 @@ public Timeline(Drawable userContent) } [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) + private void load(IBindable beatmap, OsuColour colours, OverlayColourProvider colourProvider, OsuConfigManager config) { CentreMarker centreMarker; // We don't want the centre marker to scroll AddInternal(centreMarker = new CentreMarker()); - ticks = new TimelineTickDisplay - { - Padding = new MarginPadding { Vertical = 2, }, - }; + ticks = new TimelineTickDisplay(); AddRange(new Drawable[] { - ticks = new TimelineTickDisplay(), + ticks, controlPoints = new TimelineTimingChangeDisplay { - RelativeSizeAxes = Axes.X, - Height = timeline_expanded_height - timeline_height, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, - ticks, mainContent = new Container { RelativeSizeAxes = Axes.X, Height = timeline_height, - Depth = float.MaxValue, Children = new[] { waveform = new WaveformGraph @@ -139,19 +137,19 @@ private void load(IBindable beatmap, OsuColour colours, OsuConfi MidColour = colours.BlueDark, HighColour = colours.BlueDarker, }, - ticks.CreateProxy(), centreMarker.CreateProxy(), - new Box - { - Name = "zero marker", - RelativeSizeAxes = Axes.Y, - Width = 2, - Origin = Anchor.TopCentre, - Colour = colours.YellowDarker, - }, + ticks.CreateProxy(), userContent, } }, + new Box + { + Name = "zero marker", + RelativeSizeAxes = Axes.Y, + Width = TimelineTickDisplay.TICK_WIDTH / 2, + Origin = Anchor.TopCentre, + Colour = colourProvider.Background1, + }, }); waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); @@ -195,7 +193,7 @@ protected override void LoadComplete() if (visible.NewValue || alwaysShowControlPoints) { this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); - mainContent.MoveToY(15, 200, Easing.OutQuint); + mainContent.MoveToY(0, 200, Easing.OutQuint); // delay the fade in else masking looks weird. controlPoints.Delay(180).FadeIn(400, Easing.OutQuint); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 4796c08809ff..66d0df9e1800 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -17,6 +17,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class TimelineTickDisplay : TimelinePart { + public const float TICK_WIDTH = 3; + // With current implementation every tick in the sub-tree should be visible, no need to check whether they are masked away. public override bool UpdateSubTreeMasking() => false; @@ -138,20 +140,15 @@ private void createTicks() // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. - Vector2 size = Vector2.One; - - if (indexInBar != 0) - size = BindableBeatDivisor.GetSize(divisor); + var size = indexInBar == 0 + ? new Vector2(1.3f, 1) + : BindableBeatDivisor.GetSize(divisor); var line = getNextUsableLine(); line.X = xPos; - line.Anchor = Anchor.CentreLeft; - line.Origin = Anchor.Centre; - - line.Height = 0.6f + size.Y * 0.4f; - line.Width = PointVisualisation.MAX_WIDTH * (0.6f + 0.4f * size.X); - + line.Width = TICK_WIDTH * size.X; + line.Height = size.Y; line.Colour = colour; } @@ -174,8 +171,15 @@ private void createTicks() Drawable getNextUsableLine() { PointVisualisation point; + if (drawableIndex >= Count) - Add(point = new PointVisualisation(0)); + { + Add(point = new PointVisualisation(0) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + }); + } else point = Children[drawableIndex]; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTimingChangeDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTimingChangeDisplay.cs index 908aa6bc76f5..419f7e111f7a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTimingChangeDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTimingChangeDisplay.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -122,8 +121,6 @@ public TimingPointPiece(TimingControlPoint timingPoint) [BackgroundDependencyLoader] private void load(OsuColour colours) { - const float corner_radius = PointVisualisation.MAX_WIDTH / 2; - InternalChildren = new Drawable[] { new Container @@ -131,7 +128,7 @@ private void load(OsuColour colours) RelativeSizeAxes = Axes.Both, Colour = Point.GetRepresentingColour(colours), Masking = true, - CornerRadius = corner_radius, + CornerRadius = TimelineTickDisplay.TICK_WIDTH / 2, Child = new Box { Colour = Color4.White, From fef56cc29eeca9d0af0a6ae5daadd8a5505cd324 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2024 15:57:52 +0900 Subject: [PATCH 356/579] Remove expanding behaviour of timeline completely --- .../Compose/Components/Timeline/Timeline.cs | 51 ++----------------- .../Screens/Edit/EditorScreenWithTimeline.cs | 10 +--- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 8 --- 3 files changed, 4 insertions(+), 65 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 3fa9fc8e3df2..840f1311dbc9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; using osuTK; using osuTK.Input; @@ -26,25 +25,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider { private const float timeline_height = 80; - private const float timeline_expanded_height = 80; private readonly Drawable userContent; - private bool alwaysShowControlPoints; - - public bool AlwaysShowControlPoints - { - get => alwaysShowControlPoints; - set - { - if (value == alwaysShowControlPoints) - return; - - alwaysShowControlPoints = value; - controlPointsVisible.TriggerChange(); - } - } - [Resolved] private EditorClock editorClock { get; set; } = null!; @@ -80,12 +63,7 @@ public bool AlwaysShowControlPoints private TimelineTickDisplay ticks = null!; - private TimelineTimingChangeDisplay controlPoints = null!; - - private Container mainContent = null!; - private Bindable waveformOpacity = null!; - private Bindable controlPointsVisible = null!; private Bindable ticksVisible = null!; private double trackLengthForZoom; @@ -112,18 +90,16 @@ private void load(IBindable beatmap, OsuColour colours, OverlayC // We don't want the centre marker to scroll AddInternal(centreMarker = new CentreMarker()); - ticks = new TimelineTickDisplay(); - AddRange(new Drawable[] { - ticks, - controlPoints = new TimelineTimingChangeDisplay + ticks = new TimelineTickDisplay(), + new TimelineTimingChangeDisplay { RelativeSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - mainContent = new Container + new Container { RelativeSizeAxes = Axes.X, Height = timeline_height, @@ -153,7 +129,6 @@ private void load(IBindable beatmap, OsuColour colours, OverlayC }); waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); - controlPointsVisible = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); ticksVisible = config.GetBindable(OsuSetting.EditorTimelineShowTicks); track.BindTo(editorClock.Track); @@ -187,26 +162,6 @@ protected override void LoadComplete() waveformOpacity.BindValueChanged(_ => updateWaveformOpacity(), true); ticksVisible.BindValueChanged(visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); - - controlPointsVisible.BindValueChanged(visible => - { - if (visible.NewValue || alwaysShowControlPoints) - { - this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); - mainContent.MoveToY(0, 200, Easing.OutQuint); - - // delay the fade in else masking looks weird. - controlPoints.Delay(180).FadeIn(400, Easing.OutQuint); - } - else - { - controlPoints.FadeOut(200, Easing.OutQuint); - - // likewise, delay the resize until the fade is complete. - this.Delay(180).ResizeHeightTo(timeline_height, 200, Easing.OutQuint); - mainContent.Delay(180).MoveToY(0, 200, Easing.OutQuint); - } - }, true); } private void updateWaveformOpacity() => diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 01908e45c7cc..5bbf293e0afa 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -106,18 +106,10 @@ protected override void LoadComplete() MainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timeline => - { - ConfigureTimeline(timeline); - timelineContent.Add(timeline); - }); + LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timelineContent.Add); }); } - protected virtual void ConfigureTimeline(TimelineArea timelineArea) - { - } - protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 67d4429be835..3f911f506722 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit.Timing { @@ -54,12 +53,5 @@ protected override void LoadComplete() SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time); } } - - protected override void ConfigureTimeline(TimelineArea timelineArea) - { - base.ConfigureTimeline(timelineArea); - - timelineArea.Timeline.AlwaysShowControlPoints = true; - } } } From 3d5b57454efbad0da9bf19a099f6bf7b311a7965 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Wed, 21 Aug 2024 16:21:49 +0800 Subject: [PATCH 357/579] Fix null reference --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 70c82576cce6..dbdeaf442a84 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -105,7 +105,8 @@ private void load(BeatmapManager? manager, SongSelect? songSelect, Clipboard cli if (manager != null) hideRequested = manager.Hide; - copyBeatmapSetUrl += () => clipboard.SetText($@"{api.WebsiteRootUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}"); + if (beatmapInfo.BeatmapSet != null) + copyBeatmapSetUrl += () => clipboard.SetText($@"{api.WebsiteRootUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}"); Header.Children = new Drawable[] { From c92af710297fb7596ef34812e31b0aa442929234 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Aug 2024 17:30:26 +0900 Subject: [PATCH 358/579] Add in-gameplay version of kiai star fountains/burst --- .../Visual/Menus/TestSceneStarFountain.cs | 39 ++++++-- osu.Game/Screens/Menu/StarFountain.cs | 28 ++++-- .../Screens/Play/KiaiGameplayFountains.cs | 94 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 16 +++- 4 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Screens/Play/KiaiGameplayFountains.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index bb327e596223..36e93756970e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -4,17 +4,17 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Menus { [TestFixture] public partial class TestSceneStarFountain : OsuTestScene { - [SetUpSteps] - public void SetUpSteps() + [Test] + public void TestMenu() { AddStep("make fountains", () => { @@ -34,11 +34,7 @@ public void SetUpSteps() }, }; }); - } - [Test] - public void TestPew() - { AddRepeatStep("activate fountains sometimes", () => { foreach (var fountain in Children.OfType()) @@ -48,5 +44,34 @@ public void TestPew() } }, 150); } + + [Test] + public void TestGameplay() + { + AddStep("make fountains", () => + { + Children = new[] + { + new KiaiGameplayFountains.GameplayStarFountain + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + X = 75, + }, + new KiaiGameplayFountains.GameplayStarFountain + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -75, + }, + }; + }); + + AddRepeatStep("activate fountains", () => + { + ((StarFountain)Children[0]).Shoot(1); + ((StarFountain)Children[1]).Shoot(-1); + }, 150); + } } } diff --git a/osu.Game/Screens/Menu/StarFountain.cs b/osu.Game/Screens/Menu/StarFountain.cs index dd5171c6be2c..92e9dd6df907 100644 --- a/osu.Game/Screens/Menu/StarFountain.cs +++ b/osu.Game/Screens/Menu/StarFountain.cs @@ -21,9 +21,11 @@ public partial class StarFountain : SkinReloadableDrawable [BackgroundDependencyLoader] private void load() { - InternalChild = spewer = new StarFountainSpewer(); + InternalChild = spewer = CreateSpewer(); } + protected virtual StarFountainSpewer CreateSpewer() => new StarFountainSpewer(); + public void Shoot(int direction) => spewer.Shoot(direction); protected override void SkinChanged(ISkinSource skin) @@ -38,17 +40,23 @@ public partial class StarFountainSpewer : ParticleSpewer private const int particle_duration_max = 1000; private double? lastShootTime; - private int lastShootDirection; + + protected int LastShootDirection { get; private set; } protected override float ParticleGravity => 800; - private const double shoot_duration = 800; + protected virtual double ShootDuration => 800; [Resolved] private ISkinSource skin { get; set; } = null!; public StarFountainSpewer() - : base(null, 240, particle_duration_max) + : this(240) + { + } + + protected StarFountainSpewer(int perSecond) + : base(null, perSecond, particle_duration_max) { } @@ -67,16 +75,16 @@ protected override FallingParticle CreateParticle() StartAngle = getRandomVariance(4), EndAngle = getRandomVariance(2), EndScale = 2.2f + getRandomVariance(0.4f), - Velocity = new Vector2(getCurrentAngle(), -1400 + getRandomVariance(100)), + Velocity = new Vector2(GetCurrentAngle(), -1400 + getRandomVariance(100)), }; } - private float getCurrentAngle() + protected virtual float GetCurrentAngle() { - const float x_velocity_from_direction = 500; const float x_velocity_random_variance = 60; + const float x_velocity_from_direction = 500; - return lastShootDirection * x_velocity_from_direction * (float)(1 - 2 * (Clock.CurrentTime - lastShootTime!.Value) / shoot_duration) + getRandomVariance(x_velocity_random_variance); + return LastShootDirection * x_velocity_from_direction * (float)(1 - 2 * (Clock.CurrentTime - lastShootTime!.Value) / ShootDuration) + getRandomVariance(x_velocity_random_variance); } private ScheduledDelegate? deactivateDelegate; @@ -86,10 +94,10 @@ public void Shoot(int direction) Active.Value = true; deactivateDelegate?.Cancel(); - deactivateDelegate = Scheduler.AddDelayed(() => Active.Value = false, shoot_duration); + deactivateDelegate = Scheduler.AddDelayed(() => Active.Value = false, ShootDuration); lastShootTime = Clock.CurrentTime; - lastShootDirection = direction; + LastShootDirection = direction; } private static float getRandomVariance(float variance) => RNG.NextSingle(-variance, variance); diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs new file mode 100644 index 000000000000..7659c61123d2 --- /dev/null +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -0,0 +1,94 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; +using osu.Game.Screens.Menu; + +namespace osu.Game.Screens.Play +{ + public partial class KiaiGameplayFountains : BeatSyncedContainer + { + private StarFountain leftFountain = null!; + private StarFountain rightFountain = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + Children = new[] + { + leftFountain = new GameplayStarFountain + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + X = 75, + }, + rightFountain = new GameplayStarFountain + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -75, + }, + }; + } + + private bool isTriggered; + + private double? lastTrigger; + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (effectPoint.KiaiMode && !isTriggered) + { + bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - effectPoint.Time) < 500; + if (isNearEffectPoint) + Shoot(); + } + + isTriggered = effectPoint.KiaiMode; + } + + public void Shoot() + { + if (lastTrigger != null && Clock.CurrentTime - lastTrigger < 500) + return; + + leftFountain.Shoot(1); + rightFountain.Shoot(-1); + lastTrigger = Clock.CurrentTime; + } + + public partial class GameplayStarFountain : StarFountain + { + protected override StarFountainSpewer CreateSpewer() => new GameplayStarFountainSpewer(); + + private partial class GameplayStarFountainSpewer : StarFountainSpewer + { + protected override double ShootDuration => 400; + + public GameplayStarFountainSpewer() + : base(perSecond: 180) + { + } + + protected override float GetCurrentAngle() + { + const float x_velocity_from_direction = 450; + const float x_velocity_to_direction = 600; + + return LastShootDirection * RNG.NextSingle(x_velocity_from_direction, x_velocity_to_direction); + } + } + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9a3d83782f59..05f101f20c69 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -405,8 +405,20 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); - private Drawable createUnderlayComponents() => - DimmableStoryboard = new DimmableStoryboard(GameplayState.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both }; + private Drawable createUnderlayComponents() + { + var container = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + DimmableStoryboard = new DimmableStoryboard(GameplayState.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both }, + new KiaiGameplayFountains(), + }, + }; + + return container; + } private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay) { From eefd7cf0833e3e18a40b698c5a090b7934ec1205 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 12:03:15 +0200 Subject: [PATCH 359/579] add back protection against perfect curve segments with > 3 points --- .../Objects/Legacy/ConvertHitObjectParser.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d4e30538561c..c518a3e8b28d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -347,17 +347,23 @@ private IEnumerable> convertPoints(PathType type, vertices[i] = new PathControlPoint { Position = points[i] }; // Edge-case rules (to match stable). - if (type == PathType.PERFECT_CURVE && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) + if (type == PathType.PERFECT_CURVE) { int endPointLength = endPoint == null ? 0 : 1; - if (vertices.Length + endPointLength != 3) - type = PathType.BEZIER; - else if (isLinear(points[0], points[1], endPoint ?? points[2])) + if (FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) { - // osu-stable special-cased colinear perfect curves to a linear path - type = PathType.LINEAR; + if (vertices.Length + endPointLength != 3) + type = PathType.BEZIER; + else if (isLinear(points[0], points[1], endPoint ?? points[2])) + { + // osu-stable special-cased colinear perfect curves to a linear path + type = PathType.LINEAR; + } } + else if (vertices.Length + endPointLength > 3) + // Lazer supports perfect curves with less than 3 points and colinear points + type = PathType.BEZIER; } // The first control point must have a definite type. From 28d0a245556e3be98ad2d3612358d86ead9e0e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Aug 2024 12:27:56 +0200 Subject: [PATCH 360/579] Fix the fix The more proper way to do this would be to address the underlying issue, which is https://github.com/ppy/osu/issues/29546, but let's do this locally for now. --- osu.Game/Screens/Ranking/FavouriteButton.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index bb4f25080c53..aecaf7c5b92f 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -78,8 +78,11 @@ private void getBeatmapSet() { Logger.Error(e, $"Failed to fetch beatmap info: {e.Message}"); - Schedule(() => loading.Hide()); - Enabled.Value = false; + Schedule(() => + { + loading.Hide(); + Enabled.Value = false; + }); }; api.Queue(beatmapSetRequest); } @@ -109,8 +112,12 @@ private void toggleFavouriteStatus() favouriteRequest.Failure += e => { Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}"); - Enabled.Value = true; - loading.Hide(); + + Schedule(() => + { + Enabled.Value = true; + loading.Hide(); + }); }; api.Queue(favouriteRequest); From 094b184191d241212a673c7074cdc8d85d2ee863 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 12:28:56 +0200 Subject: [PATCH 361/579] snap the slider duration in normal drag --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 785febab4be9..691c053e4d13 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -265,6 +265,8 @@ private void adjustLength(double proposedDistance, bool adjustVelocity) else { double minDistance = distanceSnapProvider?.GetBeatSnapDistanceAt(HitObject, false) * oldVelocityMultiplier ?? 1; + // Add a small amount to the proposed distance to make it easier to snap to the full length of the slider. + proposedDistance = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)proposedDistance + 1) ?? proposedDistance; proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance); } From 423feadd64032a0dd6bfb08302c3aba58d7e2798 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 14:12:58 +0200 Subject: [PATCH 362/579] Revert "add arrow indicator" This reverts commit 1ce9e97fd45bb81f13a8e6a799af43d6342922af. --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 9c42d072d128..6cd70449432a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Audio; @@ -166,13 +165,6 @@ public partial class SampleEditPopover : OsuPopover [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } = null!; - protected override Drawable CreateArrow() => new Triangle - { - Size = new Vector2(20), - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - }; - public SampleEditPopover(HitObject hitObject) { this.hitObject = hitObject; From 5f88435d960e18aa0f7121801ac144940cee3efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Aug 2024 15:28:51 +0200 Subject: [PATCH 363/579] Add support for retrieving submit/rank date from local metadata cache in version 2 Closes https://github.com/ppy/osu/issues/22416. --- .../LocalCachedBeatmapMetadataSource.cs | 149 ++++++++++++++---- 1 file changed, 119 insertions(+), 30 deletions(-) diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index 27bc80344930..96817571f6eb 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -80,6 +80,8 @@ private bool shouldFetchCache() public bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) { + Debug.Assert(beatmapInfo.BeatmapSet != null); + if (!Available) { onlineMetadata = null; @@ -94,43 +96,21 @@ public bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? online return false; } - Debug.Assert(beatmapInfo.BeatmapSet != null); - try { using (var db = new SqliteConnection(string.Concat(@"Data Source=", storage.GetFullPath(@"online.db", true)))) { db.Open(); - using (var cmd = db.CreateCommand()) + switch (getCacheVersion(db)) { - cmd.CommandText = - @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; - - cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); - cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - { - logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo}."); - - onlineMetadata = new OnlineBeatmapMetadata - { - BeatmapSetID = reader.GetInt32(0), - BeatmapID = reader.GetInt32(1), - BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2), - BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2), - AuthorID = reader.GetInt32(3), - MD5Hash = reader.GetString(4), - LastUpdated = reader.GetDateTimeOffset(5), - // TODO: DateSubmitted and DateRanked are not provided by local cache. - }; - return true; - } - } + case 1: + // will eventually become irrelevant due to the monthly recycling of local caches + // can be removed 20250221 + return queryCacheVersion1(db, beatmapInfo, out onlineMetadata); + + case 2: + return queryCacheVersion2(db, beatmapInfo, out onlineMetadata); } } } @@ -211,6 +191,115 @@ private void prepareLocalCache() }); } + private int getCacheVersion(SqliteConnection connection) + { + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = @"SELECT COUNT(1) FROM `sqlite_master` WHERE `type` = 'table' AND `name` = 'schema_version'"; + + using var reader = cmd.ExecuteReader(); + + if (!reader.Read()) + throw new InvalidOperationException("Error when attempting to check for existence of `schema_version` table."); + + // No versioning table means that this is the very first version of the schema. + if (reader.GetInt32(0) == 0) + return 1; + } + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = @"SELECT `number` FROM `schema_version`"; + + using var reader = cmd.ExecuteReader(); + + if (!reader.Read()) + throw new InvalidOperationException("Error when attempting to query schema version."); + + return reader.GetInt32(0); + } + } + + private bool queryCacheVersion1(SqliteConnection db, BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) + { + Debug.Assert(beatmapInfo.BeatmapSet != null); + + using var cmd = db.CreateCommand(); + + cmd.CommandText = + @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + + cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); + cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); + cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); + + using var reader = cmd.ExecuteReader(); + + if (reader.Read()) + { + logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo} (cache version 1)."); + + onlineMetadata = new OnlineBeatmapMetadata + { + BeatmapSetID = reader.GetInt32(0), + BeatmapID = reader.GetInt32(1), + BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2), + BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2), + AuthorID = reader.GetInt32(3), + MD5Hash = reader.GetString(4), + LastUpdated = reader.GetDateTimeOffset(5), + // TODO: DateSubmitted and DateRanked are not provided by local cache in this version. + }; + return true; + } + + onlineMetadata = null; + return false; + } + + private bool queryCacheVersion2(SqliteConnection db, BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) + { + Debug.Assert(beatmapInfo.BeatmapSet != null); + + using var cmd = db.CreateCommand(); + + cmd.CommandText = + """ + SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date` + FROM `osu_beatmaps` AS `b` + JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id` + WHERE `b`.`checksum` = @MD5Hash OR `b`.`beatmap_id` = @OnlineID OR `b`.`filename` = @Path + """; + + cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); + cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); + cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); + + using var reader = cmd.ExecuteReader(); + + if (reader.Read()) + { + logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo} (cache version 2)."); + + onlineMetadata = new OnlineBeatmapMetadata + { + BeatmapSetID = reader.GetInt32(0), + BeatmapID = reader.GetInt32(1), + BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2), + BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2), + AuthorID = reader.GetInt32(3), + MD5Hash = reader.GetString(4), + LastUpdated = reader.GetDateTimeOffset(5), + DateSubmitted = reader.GetDateTimeOffset(6), + DateRanked = reader.GetDateTimeOffset(7), + }; + return true; + } + + onlineMetadata = null; + return false; + } + private static void log(string message) => Logger.Log($@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}", LoggingTarget.Database); From 843b10ef34a222ff938bc904597569f1862b8e5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 01:05:47 +0900 Subject: [PATCH 364/579] Add back incorrectly removed control point display toggle --- .../Compose/Components/Timeline/Timeline.cs | 29 ++++++++++++++++++- .../Screens/Edit/EditorScreenWithTimeline.cs | 10 ++++++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 8 +++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 840f1311dbc9..a9b0b5c286a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -28,6 +28,21 @@ public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider private readonly Drawable userContent; + private bool alwaysShowControlPoints; + + public bool AlwaysShowControlPoints + { + get => alwaysShowControlPoints; + set + { + if (value == alwaysShowControlPoints) + return; + + alwaysShowControlPoints = value; + controlPointsVisible.TriggerChange(); + } + } + [Resolved] private EditorClock editorClock { get; set; } = null!; @@ -63,7 +78,10 @@ public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider private TimelineTickDisplay ticks = null!; + private TimelineTimingChangeDisplay controlPoints = null!; + private Bindable waveformOpacity = null!; + private Bindable controlPointsVisible = null!; private Bindable ticksVisible = null!; private double trackLengthForZoom; @@ -93,7 +111,7 @@ private void load(IBindable beatmap, OsuColour colours, OverlayC AddRange(new Drawable[] { ticks = new TimelineTickDisplay(), - new TimelineTimingChangeDisplay + controlPoints = new TimelineTimingChangeDisplay { RelativeSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, @@ -129,6 +147,7 @@ private void load(IBindable beatmap, OsuColour colours, OverlayC }); waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); + controlPointsVisible = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); ticksVisible = config.GetBindable(OsuSetting.EditorTimelineShowTicks); track.BindTo(editorClock.Track); @@ -162,6 +181,14 @@ protected override void LoadComplete() waveformOpacity.BindValueChanged(_ => updateWaveformOpacity(), true); ticksVisible.BindValueChanged(visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); + + controlPointsVisible.BindValueChanged(visible => + { + if (visible.NewValue || alwaysShowControlPoints) + controlPoints.FadeIn(400, Easing.OutQuint); + else + controlPoints.FadeOut(200, Easing.OutQuint); + }, true); } private void updateWaveformOpacity() => diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 5bbf293e0afa..01908e45c7cc 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -106,10 +106,18 @@ protected override void LoadComplete() MainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timelineContent.Add); + LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timeline => + { + ConfigureTimeline(timeline); + timelineContent.Add(timeline); + }); }); } + protected virtual void ConfigureTimeline(TimelineArea timelineArea) + { + } + protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 3f911f506722..67d4429be835 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit.Timing { @@ -53,5 +54,12 @@ protected override void LoadComplete() SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time); } } + + protected override void ConfigureTimeline(TimelineArea timelineArea) + { + base.ConfigureTimeline(timelineArea); + + timelineArea.Timeline.AlwaysShowControlPoints = true; + } } } From fb5fb78fd31fc5ead2106a641d14595c4a299203 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 01:09:22 +0900 Subject: [PATCH 365/579] Move zero marker below control points to avoid common overlap scenario --- .../Edit/Compose/Components/Timeline/Timeline.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index a9b0b5c286a7..aea8d0283802 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -111,6 +111,14 @@ private void load(IBindable beatmap, OsuColour colours, OverlayC AddRange(new Drawable[] { ticks = new TimelineTickDisplay(), + new Box + { + Name = "zero marker", + RelativeSizeAxes = Axes.Y, + Width = TimelineTickDisplay.TICK_WIDTH / 2, + Origin = Anchor.TopCentre, + Colour = colourProvider.Background1, + }, controlPoints = new TimelineTimingChangeDisplay { RelativeSizeAxes = Axes.Both, @@ -136,14 +144,6 @@ private void load(IBindable beatmap, OsuColour colours, OverlayC userContent, } }, - new Box - { - Name = "zero marker", - RelativeSizeAxes = Axes.Y, - Width = TimelineTickDisplay.TICK_WIDTH / 2, - Origin = Anchor.TopCentre, - Colour = colourProvider.Background1, - }, }); waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); From 18a3ab2ffd4364813f094f42161070b757275f50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 01:45:43 +0900 Subject: [PATCH 366/579] Use "link" instead of "URL" --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- osu.Game/Online/Chat/ExternalLinkOpener.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 7ba3d5516250..dd0b906a17be 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -86,7 +86,7 @@ public MenuItem[] ContextMenuItems if (Link != null) { items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => host.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); + items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, copyUrl)); } return items.ToArray(); diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 82ad4215c216..90fec5fafd7f 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -60,7 +60,7 @@ public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copy }, new PopupDialogCancelButton { - Text = @"Copy URL to the clipboard", + Text = @"Copy link", Action = copyExternalLinkAction }, new PopupDialogCancelButton diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index dbdeaf442a84..851446c3e03b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -294,7 +294,7 @@ public MenuItem[] ContextMenuItems items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyBeatmapSetUrl?.Invoke())); + items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => copyBeatmapSetUrl?.Invoke())); if (hideRequested != null) items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 12db8f663a4f..5f4edaf0702b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -294,7 +294,7 @@ public MenuItem[] ContextMenuItems if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyBeatmapSetUrl?.Invoke())); + items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => copyBeatmapSetUrl?.Invoke())); if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); From 87123d99bf43803f466b3eee5949cf8c8e5406a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 02:08:01 +0900 Subject: [PATCH 367/579] Move URL implementation to extension methods and share with other usages --- osu.Game/Beatmaps/BeatmapInfoExtensions.cs | 13 +++++++++++++ osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs | 16 ++++++++++++++++ .../BeatmapSet/BeatmapSetHeaderContent.cs | 3 ++- .../Select/Carousel/DrawableCarouselBeatmap.cs | 15 +++++++++------ .../Carousel/DrawableCarouselBeatmapSet.cs | 17 ++++++++++++----- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index b00d0ba31609..a82a288239ba 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Localisation; +using osu.Game.Online.API; +using osu.Game.Rulesets; using osu.Game.Screens.Select; namespace osu.Game.Beatmaps @@ -48,5 +50,16 @@ public static bool Match(this IBeatmapInfo beatmapInfo, params FilterCriteria.Op } private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]"; + + /// + /// Get the beatmap info page URL, or null if unavailable. + /// + public static string? GetOnlineURL(this IBeatmapInfo beatmapInfo, IAPIProvider api, IRulesetInfo? ruleset = null) + { + if (beatmapInfo.OnlineID <= 0 || beatmapInfo.BeatmapSet == null) + return null; + + return $@"{api.WebsiteRootUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{ruleset?.ShortName ?? beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}"; + } } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs index 965544da409b..8a107ed4864c 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs @@ -6,6 +6,8 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Models; +using osu.Game.Online.API; +using osu.Game.Rulesets; namespace osu.Game.Beatmaps { @@ -29,5 +31,19 @@ public static class BeatmapSetInfoExtensions /// The name of the file to get the storage path of. public static RealmNamedFileUsage? GetFile(this IHasRealmFiles model, string filename) => model.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase)); + + /// + /// Get the beatmapset info page URL, or null if unavailable. + /// + public static string? GetOnlineURL(this IBeatmapSetInfo beatmapSetInfo, IAPIProvider api, IRulesetInfo? ruleset = null) + { + if (beatmapSetInfo.OnlineID <= 0) + return null; + + if (ruleset != null) + return $@"{api.WebsiteRootUrl}/beatmapsets/{beatmapSetInfo.OnlineID}#{ruleset.ShortName}"; + + return $@"{api.WebsiteRootUrl}/beatmapsets/{beatmapSetInfo.OnlineID}"; + } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 7ff8352054a7..168056ea58d1 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -200,7 +200,8 @@ public BeatmapSetHeaderContent() private void updateExternalLink() { - if (externalLink != null) externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{Picker.Beatmap.Value?.Ruleset.ShortName}/{Picker.Beatmap.Value?.OnlineID}"; + if (externalLink != null) + externalLink.Link = Picker.Beatmap.Value?.GetOnlineURL(api) ?? BeatmapSet.Value?.GetOnlineURL(api); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 851446c3e03b..dd9f2226e902 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -55,7 +55,6 @@ public partial class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContext private Action? selectRequested; private Action? hideRequested; - private Action? copyBeatmapSetUrl; private Triangles triangles = null!; @@ -82,6 +81,12 @@ public partial class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContext [Resolved] private IBindable> mods { get; set; } = null!; + [Resolved] + private Clipboard clipboard { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + private IBindable starDifficultyBindable = null!; private CancellationTokenSource? starDifficultyCancellationSource; @@ -92,7 +97,7 @@ public DrawableCarouselBeatmap(CarouselBeatmap panel) } [BackgroundDependencyLoader] - private void load(BeatmapManager? manager, SongSelect? songSelect, Clipboard clipboard, IAPIProvider api) + private void load(BeatmapManager? manager, SongSelect? songSelect, IAPIProvider api) { Header.Height = height; @@ -105,9 +110,6 @@ private void load(BeatmapManager? manager, SongSelect? songSelect, Clipboard cli if (manager != null) hideRequested = manager.Hide; - if (beatmapInfo.BeatmapSet != null) - copyBeatmapSetUrl += () => clipboard.SetText($@"{api.WebsiteRootUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}"); - Header.Children = new Drawable[] { background = new Box @@ -294,7 +296,8 @@ public MenuItem[] ContextMenuItems items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => copyBeatmapSetUrl?.Invoke())); + if (beatmapInfo.GetOnlineURL(api) is string url) + items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => clipboard.SetText(url))); if (hideRequested != null) items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 5f4edaf0702b..323334799143 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -33,7 +33,6 @@ public partial class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasCont private Action restoreHiddenRequested = null!; private Action? viewDetails; - private Action? copyBeatmapSetUrl; [Resolved] private IDialogOverlay? dialogOverlay { get; set; } @@ -44,6 +43,15 @@ public partial class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasCont [Resolved] private RealmAccess realm { get; set; } = null!; + [Resolved] + private Clipboard clipboard { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved] + private IBindable ruleset { get; set; } = null!; + public IEnumerable DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty() : beatmapContainer.AliveChildren; private Container? beatmapContainer; @@ -70,7 +78,7 @@ protected override void FreeAfterUse() } [BackgroundDependencyLoader] - private void load(BeatmapSetOverlay? beatmapOverlay, SongSelect? songSelect, Clipboard clipboard, IBindable ruleset, IAPIProvider api) + private void load(BeatmapSetOverlay? beatmapOverlay, SongSelect? songSelect) { if (songSelect != null) mainMenuItems = songSelect.CreateForwardNavigationMenuItemsForBeatmap(() => (((CarouselBeatmapSet)Item!).GetNextToSelect() as CarouselBeatmap)!.BeatmapInfo); @@ -83,8 +91,6 @@ private void load(BeatmapSetOverlay? beatmapOverlay, SongSelect? songSelect, Cli if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; - - copyBeatmapSetUrl += () => clipboard.SetText($@"{api.WebsiteRootUrl}/beatmapsets/{beatmapSet.OnlineID}#{ruleset.Value.ShortName}"); } protected override void Update() @@ -294,7 +300,8 @@ public MenuItem[] ContextMenuItems if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); - items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => copyBeatmapSetUrl?.Invoke())); + if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url) + items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => clipboard.SetText(url))); if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); From ac5a3a095919b48d9777a2828f8fb989251fed0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 02:17:11 +0900 Subject: [PATCH 368/579] Remove one unused parameter --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index dd9f2226e902..89ace49ccd1b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -97,7 +97,7 @@ public DrawableCarouselBeatmap(CarouselBeatmap panel) } [BackgroundDependencyLoader] - private void load(BeatmapManager? manager, SongSelect? songSelect, IAPIProvider api) + private void load(BeatmapManager? manager, SongSelect? songSelect) { Header.Height = height; From fc02b4b942ef23a783a619f2493e1ff92221e3b5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Aug 2024 05:39:57 +0900 Subject: [PATCH 369/579] Alter NRT usage --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 91d7fdda7378..6cec5a35a8d5 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -215,12 +215,12 @@ public FocusGrabbingContainer(ModCustomisationPanel panel) this.panel = panel; } - private InputManager? inputManager; + private InputManager inputManager = null!; protected override void LoadComplete() { base.LoadComplete(); - inputManager = GetContainingInputManager(); + inputManager = GetContainingInputManager()!; } protected override void Update() @@ -229,7 +229,7 @@ protected override void Update() if (ExpandedState.Value == ModCustomisationPanelState.ExpandedByHover) { - if (!ReceivePositionalInputAt(inputManager!.CurrentState.Mouse.Position) && inputManager.DraggedDrawable == null) + if (!ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position) && inputManager.DraggedDrawable == null) ExpandedState.Value = ModCustomisationPanelState.Collapsed; } } From 1efa6b7221b32130803bfa0e02e781b99a33fb4a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Aug 2024 05:40:43 +0900 Subject: [PATCH 370/579] Merge if branches --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 6cec5a35a8d5..522481bc6bb5 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -227,10 +227,11 @@ protected override void Update() { base.Update(); - if (ExpandedState.Value == ModCustomisationPanelState.ExpandedByHover) + if (ExpandedState.Value == ModCustomisationPanelState.ExpandedByHover + && !ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position) + && inputManager.DraggedDrawable == null) { - if (!ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position) && inputManager.DraggedDrawable == null) - ExpandedState.Value = ModCustomisationPanelState.Collapsed; + ExpandedState.Value = ModCustomisationPanelState.Collapsed; } } } From a669c53df7ea119792c3c42007791ba5941c6635 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Aug 2024 06:00:07 +0900 Subject: [PATCH 371/579] Add failing test cases --- .../UserInterface/TestSceneMainMenuButton.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 98f2b129ff4a..e534547c27d7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Online.API; @@ -71,6 +72,7 @@ public void TestDailyChallengeButton() NotificationOverlay notificationOverlay = null!; DependencyProvidingContainer buttonContainer = null!; + AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234, @@ -96,6 +98,7 @@ public void TestDailyChallengeButton() }, }; }); + AddAssert("intro played flag reset", () => !Dependencies.Get().Get(Static.DailyChallengeIntroPlayed)); AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1)); AddStep("clear notifications", () => @@ -103,15 +106,85 @@ public void TestDailyChallengeButton() foreach (var notification in notificationOverlay.AllNotifications) notification.Close(runFlingAnimation: false); }); + + AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + AddAssert("intro played flag still set", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed)); AddStep("hide button's parent", () => buttonContainer.Hide()); + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234, })); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + AddAssert("intro played flag still set", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed)); + } + + [Test] + public void TestDailyChallengeButtonOldChallenge() + { + AddStep("set up API", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetRoomRequest getRoomRequest: + if (getRoomRequest.RoomId != 1234) + return false; + + var beatmap = CreateAPIBeatmap(); + beatmap.OnlineID = 1001; + getRoomRequest.TriggerSuccess(new Room + { + RoomID = { Value = 1234 }, + Playlist = + { + new PlaylistItem(beatmap) + }, + StartDate = { Value = DateTimeOffset.Now.AddMinutes(-50) }, + EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } + }); + return true; + + default: + return false; + } + }); + + NotificationOverlay notificationOverlay = null!; + + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); + AddStep("add content", () => + { + notificationOverlay = new NotificationOverlay(); + Children = new Drawable[] + { + notificationOverlay, + new DependencyProvidingContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)], + Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }, + }, + }; + }); + + AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo + { + RoomID = 1234 + })); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + AddAssert("intro played flag reset", () => !Dependencies.Get().Get(Static.DailyChallengeIntroPlayed)); } } } From 922814fab37f7f915f80265740640f049acd2056 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Aug 2024 06:00:37 +0900 Subject: [PATCH 372/579] Fix flag reset on connection dropouts --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index d47866ef7334..4dbebf0ae9ec 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -132,7 +132,7 @@ protected override void Update() } } - private long? lastNotifiedDailyChallengeRoomId; + private long? lastDailyChallengeRoomID; private void dailyChallengeChanged(ValueChangedEvent _) { @@ -152,19 +152,19 @@ private void dailyChallengeChanged(ValueChangedEvent _) roomRequest.Success += room => { - // force showing intro on the first time when a new daily challenge is up. - statics.SetValue(Static.DailyChallengeIntroPlayed, false); - Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; - // We only want to notify the user if a new challenge recently went live. - if (room.StartDate.Value != null - && Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800 - && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) + if (room.StartDate.Value != null && room.RoomID.Value != lastDailyChallengeRoomID) { - lastNotifiedDailyChallengeRoomId = room.RoomID.Value; - notificationOverlay?.Post(new NewDailyChallengeNotification(room)); + lastDailyChallengeRoomID = room.RoomID.Value; + + // new challenge is live, reset intro played static. + statics.SetValue(Static.DailyChallengeIntroPlayed, false); + + // we only want to notify the user if the new challenge just went live. + if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800) + notificationOverlay?.Post(new NewDailyChallengeNotification(room)); } updateCountdown(); From 7f5f3a4589acddc13e37fd901bfb36ac915592ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 13:57:19 +0900 Subject: [PATCH 373/579] Fix mod icons potentially showing incorrectly at daily challenge intro Prefer using the beatmap's rulesets over the current user selection. Closes https://github.com/ppy/osu/issues/29559. --- .../DailyChallenge/DailyChallengeIntro.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index e59031f66355..47785c8868e1 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -93,14 +93,15 @@ public DailyChallengeIntro(Room room) protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config, AudioManager audio) + private void load(RulesetStore rulesets, BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config, AudioManager audio) { const float horizontal_info_size = 500f; - Ruleset ruleset = Ruleset.Value.CreateInstance(); - StarRatingDisplay starRatingDisplay; + IBeatmapInfo beatmap = item.Beatmap; + Ruleset ruleset = rulesets.GetRuleset(item.Beatmap.Ruleset.ShortName)?.CreateInstance() ?? Ruleset.Value.CreateInstance(); + InternalChildren = new Drawable[] { beatmapAvailabilityTracker, @@ -242,13 +243,13 @@ private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader Origin = Anchor.TopCentre, Shear = new Vector2(-OsuGame.SHEAR, 0f), MaxWidth = horizontal_info_size, - Text = item.Beatmap.BeatmapSet!.Metadata.GetDisplayTitleRomanisable(false), + Text = beatmap.BeatmapSet!.Metadata.GetDisplayTitleRomanisable(false), Padding = new MarginPadding { Horizontal = 5f }, Font = OsuFont.GetFont(size: 26), }, new TruncatingSpriteText { - Text = $"Difficulty: {item.Beatmap.DifficultyName}", + Text = $"Difficulty: {beatmap.DifficultyName}", Font = OsuFont.GetFont(size: 20, italics: true), MaxWidth = horizontal_info_size, Shear = new Vector2(-OsuGame.SHEAR, 0f), @@ -257,7 +258,7 @@ private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader }, new TruncatingSpriteText { - Text = $"by {item.Beatmap.Metadata.Author.Username}", + Text = $"by {beatmap.Metadata.Author.Username}", Font = OsuFont.GetFont(size: 16, italics: true), MaxWidth = horizontal_info_size, Shear = new Vector2(-OsuGame.SHEAR, 0f), @@ -309,14 +310,14 @@ private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader } }; - starDifficulty = difficultyCache.GetBindableDifficulty(item.Beatmap); + starDifficulty = difficultyCache.GetBindableDifficulty(beatmap); starDifficulty.BindValueChanged(star => { if (star.NewValue != null) starRatingDisplay.Current.Value = star.NewValue.Value; }, true); - LoadComponentAsync(new OnlineBeatmapSetCover(item.Beatmap.BeatmapSet as IBeatmapSetOnlineInfo) + LoadComponentAsync(new OnlineBeatmapSetCover(beatmap.BeatmapSet as IBeatmapSetOnlineInfo) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -334,8 +335,8 @@ private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader if (config.Get(OsuSetting.AutomaticallyDownloadMissingBeatmaps)) { - if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = item.Beatmap.BeatmapSet!.OnlineID })) - beatmapDownloader.Download(item.Beatmap.BeatmapSet!, config.Get(OsuSetting.PreferNoVideo)); + if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmap.BeatmapSet!.OnlineID })) + beatmapDownloader.Download(beatmap.BeatmapSet!, config.Get(OsuSetting.PreferNoVideo)); } dateWindupSample = audio.Samples.Get(@"DailyChallenge/date-windup"); From f068b7a521c8ce8b29da9161f7ef4c7ab92429ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 13:43:10 +0900 Subject: [PATCH 374/579] Move copy-to-url method to `OsuGame` to centralise toast popup support --- .../UserInterface/ExternalLinkButton.cs | 23 +++++-------------- osu.Game/Localisation/ToastStrings.cs | 4 ++-- osu.Game/OsuGame.cs | 11 ++++++++- .../Carousel/DrawableCarouselBeatmap.cs | 7 +++--- .../Carousel/DrawableCarouselBeatmapSet.cs | 7 +++--- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index dd0b906a17be..806b7a10b8b9 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -10,9 +10,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Framework.Platform; -using osu.Game.Overlays; -using osu.Game.Overlays.OSD; using osuTK; using osuTK.Graphics; @@ -25,13 +22,7 @@ public partial class ExternalLinkButton : CompositeDrawable, IHasTooltip, IHasCo private Color4 hoverColour; [Resolved] - private GameHost host { get; set; } = null!; - - [Resolved] - private Clipboard clipboard { get; set; } = null!; - - [Resolved] - private OnScreenDisplay? onScreenDisplay { get; set; } + private OsuGame? game { get; set; } private readonly SpriteIcon linkIcon; @@ -71,7 +62,7 @@ protected override void OnHoverLost(HoverLostEvent e) protected override bool OnClick(ClickEvent e) { if (Link != null) - host.OpenUrlExternally(Link); + game?.OpenUrlExternally(Link); return true; } @@ -85,7 +76,7 @@ public MenuItem[] ContextMenuItems if (Link != null) { - items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => host.OpenUrlExternally(Link))); + items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => game?.OpenUrlExternally(Link))); items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, copyUrl)); } @@ -95,11 +86,9 @@ public MenuItem[] ContextMenuItems private void copyUrl() { - if (Link != null) - { - clipboard.SetText(Link); - onScreenDisplay?.Display(new CopyUrlToast()); - } + if (Link == null) return; + + game?.CopyUrlToClipboard(Link); } } } diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 942540cfc524..49e8d00371f4 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -45,9 +45,9 @@ public static class ToastStrings public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); /// - /// "URL copied" + /// "Link copied to clipboard" /// - public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); + public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"Link copied to clipboard"); /// /// "Speed changed to {0:N2}x" diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7e4d2ccf39b1..089db3b69895 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -54,6 +54,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Overlays.Music; using osu.Game.Overlays.Notifications; +using osu.Game.Overlays.OSD; using osu.Game.Overlays.SkinEditor; using osu.Game.Overlays.Toolbar; using osu.Game.Overlays.Volume; @@ -142,6 +143,8 @@ public partial class OsuGame : OsuGameBase, IKeyBindingHandler, IL private Container overlayOffsetContainer; + private OnScreenDisplay onScreenDisplay; + [Resolved] private FrameworkConfigManager frameworkConfig { get; set; } @@ -497,6 +500,12 @@ public void HandleLink(LinkDetails link) => Schedule(() => } }); + public void CopyUrlToClipboard(string url) => waitForReady(() => onScreenDisplay, _ => + { + dependencies.Get().SetText(url); + onScreenDisplay.Display(new CopyUrlToast()); + }); + public void OpenUrlExternally(string url, bool forceBypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ => { bool isTrustedDomain; @@ -1078,7 +1087,7 @@ protected override void LoadComplete() loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true); - var onScreenDisplay = new OnScreenDisplay(); + onScreenDisplay = new OnScreenDisplay(); onScreenDisplay.BeginTracking(this, frameworkConfig); onScreenDisplay.BeginTracking(this, LocalConfig); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 89ace49ccd1b..66d1480fdcd3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -17,7 +17,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Collections; @@ -82,10 +81,10 @@ public partial class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContext private IBindable> mods { get; set; } = null!; [Resolved] - private Clipboard clipboard { get; set; } = null!; + private IAPIProvider api { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } = null!; + private OsuGame? game { get; set; } private IBindable starDifficultyBindable = null!; private CancellationTokenSource? starDifficultyCancellationSource; @@ -297,7 +296,7 @@ public MenuItem[] ContextMenuItems items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); if (beatmapInfo.GetOnlineURL(api) is string url) - items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => clipboard.SetText(url))); + items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); if (hideRequested != null) items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 323334799143..1cd8b065fc8d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Platform; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Collections; @@ -44,10 +43,10 @@ public partial class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasCont private RealmAccess realm { get; set; } = null!; [Resolved] - private Clipboard clipboard { get; set; } = null!; + private IAPIProvider api { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } = null!; + private OsuGame? game { get; set; } [Resolved] private IBindable ruleset { get; set; } = null!; @@ -301,7 +300,7 @@ public MenuItem[] ContextMenuItems items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url) - items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => clipboard.SetText(url))); + items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); From 9df12e3d8750c56b0190e407c25f44db7cb6f340 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 14:15:36 +0900 Subject: [PATCH 375/579] Move seek button to left to differentiate mutating operations --- .../Screens/Edit/Timing/ControlPointList.cs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index cbef0b906480..8699c388b383 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -44,6 +44,26 @@ private void load(OsuColour colours) Groups = { BindTarget = Beatmap.ControlPointInfo.Groups, }, }, new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding(margins), + Spacing = new Vector2(5), + Children = new Drawable[] + { + new RoundedButton + { + Text = "Select closest to current time", + Action = goToCurrentGroup, + Size = new Vector2(220, 30), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, + new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, @@ -68,15 +88,6 @@ private void load(OsuColour colours) Size = new Vector2(160, 30), Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - BackgroundColour = colours.Green3, - }, - new RoundedButton - { - Text = "Go to current time", - Action = goToCurrentGroup, - Size = new Vector2(140, 30), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, }, } }, From dfb4a76e29758853c9c0cd136107b7693bbaed12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 14:05:59 +0900 Subject: [PATCH 376/579] Fix test being repeat step --- osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 36e93756970e..29fa7287d254 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -67,11 +67,11 @@ public void TestGameplay() }; }); - AddRepeatStep("activate fountains", () => + AddStep("activate fountains", () => { ((StarFountain)Children[0]).Shoot(1); ((StarFountain)Children[1]).Shoot(-1); - }, 150); + }); } } } From 236a273e09d0e05fc17561a449cd9dc2d95b2c82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 15:11:23 +0900 Subject: [PATCH 377/579] Simplify `DailyChallengeIntro` test scene Seems like some bad copy-paste in the past. Most of this is already being done in `TestSceneDailyChallenge`. --- .../TestSceneDailyChallengeIntro.cs | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index a3541d957e4a..cff2387aedb8 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -5,13 +5,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Screens; -using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.Metadata; using osu.Game.Tests.Visual.OnlinePlay; @@ -27,6 +26,8 @@ public partial class TestSceneDailyChallengeIntro : OnlinePlayTestScene [Cached(typeof(INotificationOverlay))] private NotificationOverlay notificationOverlay = new NotificationOverlay(); + private Room room = null!; + [BackgroundDependencyLoader] private void load() { @@ -35,33 +36,15 @@ private void load() } [Test] - [Solo] public void TestDailyChallenge() { - var room = new Room - { - RoomID = { Value = 1234 }, - Name = { Value = "Daily Challenge: June 4, 2024" }, - Playlist = - { - new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) - { - RequiredMods = [new APIMod(new OsuModTraceable())], - AllowedMods = [new APIMod(new OsuModDoubleTime())] - } - }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } - }; - - AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); - AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallengeIntro(room))); + startChallenge(); + AddStep("push screen", () => LoadScreen(new DailyChallengeIntro(room))); } - [Test] - public void TestNotifications() + private void startChallenge() { - var room = new Room + room = new Room { RoomID = { Value = 1234 }, Name = { Value = "Daily Challenge: June 4, 2024" }, @@ -78,12 +61,6 @@ public void TestNotifications() }; AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); - AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); - - Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!; - AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); - AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); - AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); } } } From 9b9986b6f2c508c37fb6674c7b47381fa6681148 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 15:14:52 +0900 Subject: [PATCH 378/579] Add isolated test for daily challenge intro flag --- .../DailyChallenge/TestSceneDailyChallengeIntro.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index cff2387aedb8..08d44d740521 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Game.Configuration; using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; @@ -42,6 +43,19 @@ public void TestDailyChallenge() AddStep("push screen", () => LoadScreen(new DailyChallengeIntro(room))); } + [Test] + public void TestPlayIntroOnceFlag() + { + AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); + + startChallenge(); + + AddAssert("intro played flag reset", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed), () => Is.False); + + AddStep("push screen", () => LoadScreen(new DailyChallengeIntro(room))); + AddUntilStep("intro played flag set", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed), () => Is.True); + } + private void startChallenge() { room = new Room From b3be04aff1111dbc81885da82985e017a7a73647 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 16:09:11 +0900 Subject: [PATCH 379/579] Remove "leftover files" notification when migration partly fails People were deleting files they shouldn't, causing osu! to lose track of where the real user files are. For now let's just keep things simple and not let the users know that some files got left behind. Usually the files which are left behind are minimal and it should be fine to leave this up to the user. Closes https://github.com/ppy/osu/issues/29505. --- osu.Game/Localisation/MaintenanceSettingsStrings.cs | 5 ----- osu.Game/OsuGameBase.cs | 10 ++++++++-- .../Sections/Maintenance/MigrationRunScreen.cs | 12 ------------ 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 2e5f1d29df68..03e15e839356 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -34,11 +34,6 @@ public static class MaintenanceSettingsStrings /// public static LocalisableString ProhibitedInteractDuringMigration => new TranslatableString(getKey(@"prohibited_interact_during_migration"), @"Please avoid interacting with the game!"); - /// - /// "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up." - /// - public static LocalisableString FailedCleanupNotification => new TranslatableString(getKey(@"failed_cleanup_notification"), @"Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up."); - /// /// "Please select a new location" /// diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5e4ec5a61d93..1988a06503ee 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -515,6 +515,12 @@ public virtual void AttemptExit() /// Whether a restart operation was queued. public virtual bool RestartAppWhenExited() => false; + /// + /// Perform migration of user data to a specified path. + /// + /// The path to migrate to. + /// Whether migration succeeded to completion. If false, some files were left behind. + /// public bool Migrate(string path) { Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); @@ -542,10 +548,10 @@ public bool Migrate(string path) if (!readyToRun.Wait(30000) || !success) throw new TimeoutException("Attempting to block for migration took too long."); - bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + bool? cleanupSucceeded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); Logger.Log(@"Migration complete!"); - return cleanupSucceded != false; + return cleanupSucceeded != false; } finally { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index 5b24460ac2c8..bfc9e820c6c4 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -108,18 +108,6 @@ protected override void LoadComplete() { Logger.Error(task.Exception, $"Error during migration: {task.Exception?.Message}"); } - else if (!task.GetResultSafely()) - { - notifications.Post(new SimpleNotification - { - Text = MaintenanceSettingsStrings.FailedCleanupNotification, - Activated = () => - { - originalStorage.PresentExternally(); - return true; - } - }); - } Schedule(this.Exit); }); From 67f0ea5d7dd9b41632d53ab847c351669e86ca51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 16:22:00 +0900 Subject: [PATCH 380/579] Fix flooring causing delta to not work as expected --- .../Menus/TestSceneToolbarUserButton.cs | 27 +++++++++++++++++-- .../TransientUserStatisticsUpdateDisplay.cs | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs index a81c940d82ad..71a45e239852 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs @@ -97,6 +97,7 @@ public void TestStates() public void TestTransientUserStatisticsDisplay() { AddStep("Log in", () => dummyAPI.Login("wang", "jang")); + AddStep("Gain", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); @@ -113,6 +114,7 @@ public void TestTransientUserStatisticsDisplay() PP = 1357 }); }); + AddStep("Loss", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); @@ -129,7 +131,9 @@ public void TestTransientUserStatisticsDisplay() PP = 1234 }); }); - AddStep("No change", () => + + // Tests flooring logic works as expected. + AddStep("Tiny increase in PP", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( @@ -137,7 +141,24 @@ public void TestTransientUserStatisticsDisplay() new UserStatistics { GlobalRank = 111_111, - PP = 1357 + PP = 1357.6m + }, + new UserStatistics + { + GlobalRank = 111_111, + PP = 1358.1m + }); + }); + + AddStep("No change 1", () => + { + var transientUpdateDisplay = this.ChildrenOfType().Single(); + transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( + new ScoreInfo(), + new UserStatistics + { + GlobalRank = 111_111, + PP = 1357m }, new UserStatistics { @@ -145,6 +166,7 @@ public void TestTransientUserStatisticsDisplay() PP = 1357.1m }); }); + AddStep("Was null", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); @@ -161,6 +183,7 @@ public void TestTransientUserStatisticsDisplay() PP = 1357 }); }); + AddStep("Became null", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index a25df0830973..07c2e727740d 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -83,7 +83,7 @@ protected override void LoadComplete() } if (update.After.PP != null) - pp.Display((int)(update.Before.PP ?? update.After.PP.Value), (int)Math.Abs((update.After.PP - update.Before.PP) ?? 0M), (int)update.After.PP.Value); + pp.Display((int)(update.Before.PP ?? update.After.PP.Value), (int)Math.Abs(((int?)update.After.PP - (int?)update.Before.PP) ?? 0M), (int)update.After.PP.Value); this.Delay(5000).FadeOut(500, Easing.OutQuint); }); From 9020739f3620b30f3c41875dde554fbfc7db3372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Aug 2024 10:05:45 +0200 Subject: [PATCH 381/579] Remove unused using directives --- .../Settings/Sections/Maintenance/MigrationRunScreen.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index bfc9e820c6c4..dbfca81624b1 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -6,7 +6,6 @@ using System.IO; using System.Threading.Tasks; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -16,7 +15,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; -using osu.Game.Overlays.Notifications; using osu.Game.Screens; using osuTK; From 41756520b1ff322ae8a28858becdee362279309e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 17:14:35 +0900 Subject: [PATCH 382/579] Rename `SkinComponentsContainer` to `SkinnableContainer` --- .../TestSceneCatchPlayerLegacySkin.cs | 2 +- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 4 ++-- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 12 ++++++------ .../Gameplay/TestScenePauseInputHandling.cs | 2 +- .../Visual/Gameplay/TestSceneSkinEditor.cs | 16 ++++++++-------- .../TestSceneSkinEditorComponentsList.cs | 2 +- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 4 ++-- .../Navigation/TestSceneSkinEditorNavigation.cs | 4 ++-- .../Overlays/SkinEditor/SkinComponentToolbox.cs | 4 ++-- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 14 +++++++------- osu.Game/Screens/Play/HUDOverlay.cs | 10 +++++----- osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Skinning/Skin.cs | 4 ++-- osu.Game/Skinning/SkinLayoutInfo.cs | 4 ++-- ...ponentsContainer.cs => SkinnableContainer.cs} | 6 +++--- .../Tests/Visual/LegacySkinPlayerTestScene.cs | 4 ++-- 16 files changed, 47 insertions(+), 47 deletions(-) rename osu.Game/Skinning/{SkinComponentsContainer.cs => SkinnableContainer.cs} (94%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 7812e02a6363..792caf6de69e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -23,7 +23,7 @@ public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin) 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.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index a2ce62105ecf..c9b9b97580ac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -37,7 +37,7 @@ public partial class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene public void TestEmptyLegacyBeatmapSkinFallsBack() { CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null)); - AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, skinManager.CurrentSkin.Value)); } @@ -55,7 +55,7 @@ protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSk protected bool AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea target, ISkin expectedSource) { - var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Target == target); + var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Target == target); var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer); if (actualComponentsContainer == null) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 91f22a291c43..d51c9b3f8836 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -43,7 +43,7 @@ public partial class TestSceneHUDOverlay : OsuManualInputManagerTestScene private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new TrackVirtual(60000), false, false); // best way to check without exposing. - private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); + private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); private Drawable keyCounterContent => hudOverlay.ChildrenOfType().First().ChildrenOfType().Skip(1).First(); public TestSceneHUDOverlay() @@ -242,8 +242,8 @@ public void TestHiddenHUDDoesntBlockComponentUpdates() createNew(); - 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", () => { @@ -260,10 +260,10 @@ public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad() createNew(); - 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/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index bc66947ccd2b..2c58e648310d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -286,7 +286,7 @@ private void loadPlayer(Func createRuleset) AddStep("set ruleset", () => currentRuleset = createRuleset()); AddStep("load player", LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); - AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType().All(s => s.ComponentsLoaded)); + AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType().All(s => s.ComponentsLoaded)); AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0)); AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 7466442674cf..cc514cc2fabc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -46,7 +46,7 @@ public partial class TestSceneSkinEditor : PlayerTestScene [Resolved] private SkinManager skins { get; set; } = null!; - private SkinComponentsContainer targetContainer => Player.ChildrenOfType().First(); + private SkinnableContainer targetContainer => Player.ChildrenOfType().First(); [SetUpSteps] public override void SetUpSteps() @@ -75,7 +75,7 @@ public void TestDragSelection() AddStep("Add big black boxes", () => { - var target = Player.ChildrenOfType().First(); + var target = Player.ChildrenOfType().First(); target.Add(box1 = new BigBlackBox { Position = new Vector2(-90), @@ -200,14 +200,14 @@ public void TestCyclicSelection() [Test] public void TestUndoEditHistory() { - SkinComponentsContainer firstTarget = null!; + SkinnableContainer firstTarget = null!; TestSkinEditorChangeHandler changeHandler = null!; byte[] defaultState = null!; IEnumerable testComponents = null!; AddStep("Load necessary things", () => { - firstTarget = Player.ChildrenOfType().First(); + firstTarget = Player.ChildrenOfType().First(); changeHandler = new TestSkinEditorChangeHandler(firstTarget); changeHandler.SaveState(); @@ -377,11 +377,11 @@ public void TestCopyPaste() () => Is.EqualTo(3)); } - private SkinComponentsContainer globalHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null); + private SkinnableContainer globalHUDTarget => Player.ChildrenOfType() + .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null); - private SkinComponentsContainer rulesetHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null); + private SkinnableContainer rulesetHUDTarget => Player.ChildrenOfType() + .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null); [Test] public void TestMigrationArgon() diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index b7b2a6c1750d..42dcfe12e9f4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -20,7 +20,7 @@ public partial class TestSceneSkinEditorComponentsList : SkinnableTestScene [Test] public void TestToggleEditor() { - var skinComponentsContainer = new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect)); + var skinComponentsContainer = new SkinnableContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect)); AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(skinComponentsContainer, null) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index d1e224a91052..fcaa2996e1fd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -45,7 +45,7 @@ public partial class TestSceneSkinnableHUDOverlay : SkinnableTestScene private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. - private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); + private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); private Drawable keyCounterFlow => hudOverlay.ChildrenOfType().First().ChildrenOfType>().Single(); public TestSceneSkinnableHUDOverlay() @@ -101,7 +101,7 @@ private void createNew(Action action = null) }); AddUntilStep("HUD overlay loaded", () => hudOverlay.IsAlive); AddUntilStep("components container loaded", - () => hudOverlay.ChildrenOfType().Any(scc => scc.ComponentsLoaded)); + () => hudOverlay.ChildrenOfType().Any(scc => scc.ComponentsLoaded)); } protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 38fb2846aaab..5267a57a054c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -336,13 +336,13 @@ public void TestSkinSavesOnChange() }); AddStep("change to triangles skin", () => Game.Dependencies.Get().SetSkinFromConfiguration(SkinInfo.TRIANGLES_SKIN.ToString())); - AddUntilStep("components loaded", () => Game.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddUntilStep("components loaded", () => Game.ChildrenOfType().All(c => c.ComponentsLoaded)); // sort of implicitly relies on song select not being skinnable. // TODO: revisit if the above ever changes AddUntilStep("skin changed", () => !skinEditor.ChildrenOfType().Any()); AddStep("change back to modified skin", () => Game.Dependencies.Get().SetSkinFromConfiguration(editedSkinId.ToString())); - AddUntilStep("components loaded", () => Game.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddUntilStep("components loaded", () => Game.ChildrenOfType().All(c => c.ComponentsLoaded)); AddUntilStep("changes saved", () => skinEditor.ChildrenOfType().Any()); } diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index a476fc1a6d00..85becc1a2306 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -24,7 +24,7 @@ public partial class SkinComponentToolbox : EditorSidebarSection { public Action? RequestPlacement; - private readonly SkinComponentsContainer target; + private readonly SkinnableContainer target; private readonly RulesetInfo? ruleset; @@ -35,7 +35,7 @@ public partial class SkinComponentToolbox : EditorSidebarSection /// /// The target. This is mainly used as a dependency source to find candidate components. /// A ruleset to filter components by. If null, only components which are not ruleset-specific will be included. - public SkinComponentToolbox(SkinComponentsContainer target, RulesetInfo? ruleset) + public SkinComponentToolbox(SkinnableContainer target, RulesetInfo? ruleset) : base(ruleset == null ? SkinEditorStrings.Components : LocalisableString.Interpolate($"{SkinEditorStrings.Components} ({ruleset.Name})")) { this.target = target; diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 03acf1e68c6c..78ddce03c7c4 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -472,18 +472,18 @@ private void populateSettings() settingsSidebar.Add(new SkinSettingsToolbox(component)); } - private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private IEnumerable availableTargets => targetScreen.ChildrenOfType(); - private SkinComponentsContainer? getFirstTarget() => availableTargets.FirstOrDefault(); + private SkinnableContainer? getFirstTarget() => availableTargets.FirstOrDefault(); - private SkinComponentsContainer? getTarget(SkinComponentsContainerLookup? target) + private SkinnableContainer? getTarget(SkinComponentsContainerLookup? target) { return availableTargets.FirstOrDefault(c => c.Lookup.Equals(target)); } private void revert() { - SkinComponentsContainer[] targetContainers = availableTargets.ToArray(); + SkinnableContainer[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) { @@ -555,7 +555,7 @@ private void save(Skin skin, bool userTriggered = true) if (targetScreen?.IsLoaded != true) return; - SkinComponentsContainer[] targetContainers = availableTargets.ToArray(); + SkinnableContainer[] targetContainers = availableTargets.ToArray(); if (!targetContainers.All(c => c.ComponentsLoaded)) return; @@ -600,7 +600,7 @@ public void DeleteItems(ISerialisableDrawable[] items) public void BringSelectionToFront() { - if (getTarget(selectedTarget.Value) is not SkinComponentsContainer target) + if (getTarget(selectedTarget.Value) is not SkinnableContainer target) return; changeHandler?.BeginChange(); @@ -624,7 +624,7 @@ public void BringSelectionToFront() public void SendSelectionToBack() { - if (getTarget(selectedTarget.Value) is not SkinComponentsContainer target) + if (getTarget(selectedTarget.Value) is not SkinnableContainer target) return; changeHandler?.BeginChange(); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ef3bb7c04a0f..73fda6261612 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -95,10 +95,10 @@ protected override bool ShouldBeConsideredForInput(Drawable child) private readonly BindableBool holdingForHUD = new BindableBool(); - private readonly SkinComponentsContainer mainComponents; + private readonly SkinnableContainer mainComponents; [CanBeNull] - private readonly SkinComponentsContainer rulesetComponents; + private readonly SkinnableContainer rulesetComponents; /// /// A flow which sits at the left side of the screen to house leaderboard (and related) components. @@ -132,7 +132,7 @@ public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList e) } } - private partial class HUDComponentsContainer : SkinComponentsContainer + private partial class HUDComponentsContainer : SkinnableContainer { private Bindable scoringMode; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2ee5a6f3cbf2..a4a73513386b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -321,7 +321,7 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog } } }, - new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect)) + new SkinnableContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect)) { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 3a83815f0eaf..7c205b528994 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -162,7 +162,7 @@ protected virtual void ParseConfigurationStream(Stream stream) /// Remove all stored customisations for the provided target. /// /// The target container to reset. - public void ResetDrawableTarget(SkinComponentsContainer targetContainer) + public void ResetDrawableTarget(SkinnableContainer targetContainer) { LayoutInfos.Remove(targetContainer.Lookup.Target); } @@ -171,7 +171,7 @@ public void ResetDrawableTarget(SkinComponentsContainer targetContainer) /// Update serialised information for the provided target. /// /// The target container to serialise to this skin. - public void UpdateDrawableTarget(SkinComponentsContainer targetContainer) + public void UpdateDrawableTarget(SkinnableContainer targetContainer) { if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Target, out var layoutInfo)) layoutInfos[targetContainer.Lookup.Target] = layoutInfo = new SkinLayoutInfo(); diff --git a/osu.Game/Skinning/SkinLayoutInfo.cs b/osu.Game/Skinning/SkinLayoutInfo.cs index 22c876e5ad46..bf6c69362145 100644 --- a/osu.Game/Skinning/SkinLayoutInfo.cs +++ b/osu.Game/Skinning/SkinLayoutInfo.cs @@ -11,8 +11,8 @@ namespace osu.Game.Skinning { /// - /// A serialisable model describing layout of a . - /// May contain multiple configurations for different rulesets, each of which should manifest their own as required. + /// A serialisable model describing layout of a . + /// May contain multiple configurations for different rulesets, each of which should manifest their own as required. /// [Serializable] public class SkinLayoutInfo diff --git a/osu.Game/Skinning/SkinComponentsContainer.cs b/osu.Game/Skinning/SkinnableContainer.cs similarity index 94% rename from osu.Game/Skinning/SkinComponentsContainer.cs rename to osu.Game/Skinning/SkinnableContainer.cs index 02ba43fd39cc..d2d4fac76654 100644 --- a/osu.Game/Skinning/SkinComponentsContainer.cs +++ b/osu.Game/Skinning/SkinnableContainer.cs @@ -16,10 +16,10 @@ namespace osu.Game.Skinning /// /// /// This is currently used as a means of serialising skin layouts to files. - /// Currently, one json file in a skin will represent one , containing + /// Currently, one json file in a skin will represent one , containing /// the output of . /// - public partial class SkinComponentsContainer : SkinReloadableDrawable, ISerialisableDrawableContainer + public partial class SkinnableContainer : SkinReloadableDrawable, ISerialisableDrawableContainer { private Container? content; @@ -38,7 +38,7 @@ public partial class SkinComponentsContainer : SkinReloadableDrawable, ISerialis private CancellationTokenSource? cancellationSource; - public SkinComponentsContainer(SkinComponentsContainerLookup lookup) + public SkinnableContainer(SkinComponentsContainerLookup lookup) { Lookup = lookup; } diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs index 2e254f5b95c3..0e1776be8e0f 100644 --- a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs @@ -45,13 +45,13 @@ public override void TearDownSteps() 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 9997271a6a9d9e33c99bc58a4df4566d90b6dca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Aug 2024 10:49:24 +0200 Subject: [PATCH 383/579] Fix more code quality inspections --- .../Settings/Sections/Maintenance/MigrationRunScreen.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index dbfca81624b1..e7c87a617f60 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -27,9 +27,6 @@ public partial class MigrationRunScreen : OsuScreen [Resolved(canBeNull: true)] private OsuGame game { get; set; } - [Resolved] - private INotificationOverlay notifications { get; set; } - [Resolved] private Storage storage { get; set; } @@ -97,8 +94,6 @@ protected override void LoadComplete() Beatmap.Value = Beatmap.Default; - var originalStorage = new NativeStorage(storage.GetFullPath(string.Empty), host); - migrationTask = Task.Run(PerformMigration) .ContinueWith(task => { From 1859e173f26def407cf02c610e112d49012c7004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Aug 2024 11:16:24 +0200 Subject: [PATCH 384/579] Fix EVEN MORE code quality inspections! --- .../Settings/Sections/Maintenance/MigrationRunScreen.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index e7c87a617f60..3bba480aaa0a 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; -using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -27,12 +26,6 @@ public partial class MigrationRunScreen : OsuScreen [Resolved(canBeNull: true)] private OsuGame game { get; set; } - [Resolved] - private Storage storage { get; set; } - - [Resolved] - private GameHost host { get; set; } - public override bool AllowBackButton => false; public override bool AllowExternalScreenChange => false; From f37cab0c6ec1c50fdbd5f5c7312ca0679b662388 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 18:39:36 +0900 Subject: [PATCH 385/579] Rename `SkinComponentsContainerLookup` to `GlobalSkinnableContainerLookup` --- .../Legacy/CatchLegacySkinTransformer.cs | 4 ++-- .../Argon/ManiaArgonSkinTransformer.cs | 4 ++-- .../Legacy/ManiaLegacySkinTransformer.cs | 4 ++-- .../Legacy/OsuLegacySkinTransformer.cs | 4 ++-- .../Skins/SkinDeserialisationTest.cs | 20 +++++++++---------- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 6 +++--- .../Visual/Gameplay/TestSceneSkinEditor.cs | 4 ++-- .../TestSceneSkinEditorComponentsList.cs | 2 +- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 8 ++++---- osu.Game/Screens/Play/HUDOverlay.cs | 6 +++--- osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Skinning/ArgonSkin.cs | 6 +++--- ...p.cs => GlobalSkinnableContainerLookup.cs} | 14 ++++++------- osu.Game/Skinning/LegacyBeatmapSkin.cs | 4 ++-- osu.Game/Skinning/LegacySkin.cs | 4 ++-- osu.Game/Skinning/Skin.cs | 16 +++++++-------- osu.Game/Skinning/SkinnableContainer.cs | 4 ++-- osu.Game/Skinning/TrianglesSkin.cs | 6 +++--- 18 files changed, 59 insertions(+), 59 deletions(-) rename osu.Game/Skinning/{SkinComponentsContainerLookup.cs => GlobalSkinnableContainerLookup.cs} (79%) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index f3626eb55de0..ab0420554ee9 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -30,7 +30,7 @@ public CatchLegacySkinTransformer(ISkin skin) { switch (lookup) { - case SkinComponentsContainerLookup containerLookup: + case GlobalSkinnableContainerLookup containerLookup: // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -46,7 +46,7 @@ public CatchLegacySkinTransformer(ISkin skin) // Our own ruleset components default. switch (containerLookup.Target) { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. return new DefaultSkinComponentsContainer(container => { diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index dbd690f89096..f80cb3a88ad7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -28,7 +28,7 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) { switch (lookup) { - case SkinComponentsContainerLookup containerLookup: + case GlobalSkinnableContainerLookup containerLookup: // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -39,7 +39,7 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) switch (containerLookup.Target) { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => { var combo = container.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index c25b77610aba..20017a78a297 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -80,7 +80,7 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) { switch (lookup) { - case SkinComponentsContainerLookup containerLookup: + case GlobalSkinnableContainerLookup containerLookup: // Modifications for global components. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -95,7 +95,7 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) switch (containerLookup.Target) { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => { var combo = container.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 457c191583d4..6609a84be4d2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -44,7 +44,7 @@ public OsuLegacySkinTransformer(ISkin skin) { switch (lookup) { - case SkinComponentsContainerLookup containerLookup: + case GlobalSkinnableContainerLookup containerLookup: // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -60,7 +60,7 @@ public OsuLegacySkinTransformer(ISkin skin) // Our own ruleset components default. switch (containerLookup.Target) { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => { var keyCounter = container.OfType().FirstOrDefault(); diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 534d47d617bd..ad01a057ad43 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -107,7 +107,7 @@ public void TestDeserialiseModifiedDefault() var skin = new TestSkin(new SkinInfo(), null, storage); Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2)); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9)); } } @@ -120,8 +120,8 @@ public void TestDeserialiseModifiedArgon() var skin = new TestSkin(new SkinInfo(), null, storage); Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2)); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(10)); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(PlayerName))); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(10)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(PlayerName))); } } @@ -134,10 +134,10 @@ public void TestDeserialiseModifiedClassic() var skin = new TestSkin(new SkinInfo(), null, storage); Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2)); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6)); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1)); - var skinnableInfo = skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.First(); + var skinnableInfo = skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.SongSelect].AllDrawables.First(); Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite))); Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name")); @@ -148,10 +148,10 @@ public void TestDeserialiseModifiedClassic() using (var storage = new ZipArchiveReader(stream)) { var skin = new TestSkin(new SkinInfo(), null, storage); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8)); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter))); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter))); - Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(LegacySongProgress))); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter))); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter))); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.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 c9b9b97580ac..1061f493d46d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -38,7 +38,7 @@ 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(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, skinManager.CurrentSkin.Value)); + AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents, skinManager.CurrentSkin.Value)); } protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSkin) @@ -53,7 +53,7 @@ protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSk }); } - protected bool AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea target, ISkin expectedSource) + protected bool AssertComponentsFromExpectedSource(GlobalSkinnableContainerLookup.GlobalSkinnableContainers target, ISkin expectedSource) { var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Target == target); var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer); @@ -63,7 +63,7 @@ protected bool AssertComponentsFromExpectedSource(SkinComponentsContainerLookup. var actualInfo = actualComponentsContainer.CreateSerialisedInfo(); - var expectedComponentsContainer = expectedSource.GetDrawableComponent(new SkinComponentsContainerLookup(target)) as Container; + var expectedComponentsContainer = expectedSource.GetDrawableComponent(new GlobalSkinnableContainerLookup(target)) as Container; if (expectedComponentsContainer == null) return false; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index cc514cc2fabc..9e53f86e3340 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -378,10 +378,10 @@ public void TestCopyPaste() } private SkinnableContainer globalHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null); + .Single(c => c.Lookup.Target == GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null); private SkinnableContainer rulesetHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null); + .Single(c => c.Lookup.Target == GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset != null); [Test] public void TestMigrationArgon() diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index 42dcfe12e9f4..e4b6358600bf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -20,7 +20,7 @@ public partial class TestSceneSkinEditorComponentsList : SkinnableTestScene [Test] public void TestToggleEditor() { - var skinComponentsContainer = new SkinnableContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect)); + var skinComponentsContainer = new SkinnableContainer(new GlobalSkinnableContainerLookup(GlobalSkinnableContainerLookup.GlobalSkinnableContainers.SongSelect)); AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(skinComponentsContainer, null) { diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 78ddce03c7c4..d1e9676de7fe 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -72,7 +72,7 @@ public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBind [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - private readonly Bindable selectedTarget = new Bindable(); + private readonly Bindable selectedTarget = new Bindable(); private bool hasBegunMutating; @@ -330,7 +330,7 @@ void loadBlueprintContainer() } } - private void targetChanged(ValueChangedEvent target) + private void targetChanged(ValueChangedEvent target) { foreach (var toolbox in componentsSidebar.OfType()) toolbox.Expire(); @@ -360,7 +360,7 @@ private void targetChanged(ValueChangedEvent tar { Children = new Drawable[] { - new SettingsDropdown + new SettingsDropdown { Items = availableTargets.Select(t => t.Lookup).Distinct(), Current = selectedTarget, @@ -476,7 +476,7 @@ private void populateSettings() private SkinnableContainer? getFirstTarget() => availableTargets.FirstOrDefault(); - private SkinnableContainer? getTarget(SkinComponentsContainerLookup? target) + private SkinnableContainer? getTarget(GlobalSkinnableContainerLookup? target) { return availableTargets.FirstOrDefault(c => c.Lookup.Equals(target)); } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 73fda6261612..7bddef534c61 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -109,7 +109,7 @@ protected override bool ShouldBeConsideredForInput(Drawable child) private readonly List hideTargets; /// - /// The container for skin components attached to + /// The container for skin components attached to /// internal readonly Drawable PlayfieldSkinLayer; @@ -132,7 +132,7 @@ public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList { // do stuff when we need to. @@ -111,7 +111,7 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) return songSelectComponents; - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: if (containerLookup.Ruleset != null) { return new Container diff --git a/osu.Game/Skinning/SkinComponentsContainerLookup.cs b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs similarity index 79% rename from osu.Game/Skinning/SkinComponentsContainerLookup.cs rename to osu.Game/Skinning/GlobalSkinnableContainerLookup.cs index 34358c3f0693..384b4aa23c1c 100644 --- a/osu.Game/Skinning/SkinComponentsContainerLookup.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs @@ -9,14 +9,14 @@ namespace osu.Game.Skinning { /// - /// Represents a lookup of a collection of elements that make up a particular skinnable of the game. + /// Represents a lookup of a collection of elements that make up a particular skinnable of the game. /// - public class SkinComponentsContainerLookup : ISkinComponentLookup, IEquatable + public class GlobalSkinnableContainerLookup : ISkinComponentLookup, IEquatable { /// /// The target area / layer of the game for which skin components will be returned. /// - public readonly TargetArea Target; + public readonly GlobalSkinnableContainers Target; /// /// The ruleset for which skin components should be returned. @@ -24,7 +24,7 @@ public class SkinComponentsContainerLookup : ISkinComponentLookup, IEquatable public readonly RulesetInfo? Ruleset; - public SkinComponentsContainerLookup(TargetArea target, RulesetInfo? ruleset = null) + public GlobalSkinnableContainerLookup(GlobalSkinnableContainers target, RulesetInfo? ruleset = null) { Target = target; Ruleset = ruleset; @@ -37,7 +37,7 @@ public override string ToString() return $"{Target.GetDescription()} (\"{Ruleset.Name}\" only)"; } - public bool Equals(SkinComponentsContainerLookup? other) + public bool Equals(GlobalSkinnableContainerLookup? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; @@ -51,7 +51,7 @@ public override bool Equals(object? obj) if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; - return Equals((SkinComponentsContainerLookup)obj); + return Equals((GlobalSkinnableContainerLookup)obj); } public override int GetHashCode() @@ -62,7 +62,7 @@ public override int GetHashCode() /// /// Represents a particular area or part of a game screen whose layout can be customised using the skin editor. /// - public enum TargetArea + public enum GlobalSkinnableContainers { [Description("HUD")] MainHUDComponents, diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 9cd072b60754..54e259a807e4 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -50,11 +50,11 @@ private static IResourceStore createRealmBackedStore(BeatmapInfo beatmap public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is SkinComponentsContainerLookup containerLookup) + if (lookup is GlobalSkinnableContainerLookup containerLookup) { switch (containerLookup.Target) { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.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 bbca0178d514..d9da208a7b1f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -358,13 +358,13 @@ protected override void ParseConfigurationStream(Stream stream) { switch (lookup) { - case SkinComponentsContainerLookup containerLookup: + case GlobalSkinnableContainerLookup containerLookup: if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; switch (containerLookup.Target) { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: if (containerLookup.Ruleset != null) { return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 7c205b528994..581c47402f1e 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -43,10 +43,10 @@ public abstract class Skin : IDisposable, ISkin public SkinConfiguration Configuration { get; set; } - public IDictionary LayoutInfos => layoutInfos; + public IDictionary LayoutInfos => layoutInfos; - private readonly Dictionary layoutInfos = - new Dictionary(); + private readonly Dictionary layoutInfos = + new Dictionary(); public abstract ISample? GetSample(ISampleInfo sampleInfo); @@ -123,7 +123,7 @@ protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStor } // skininfo files may be null for default skin. - foreach (SkinComponentsContainerLookup.TargetArea skinnableTarget in Enum.GetValues()) + foreach (GlobalSkinnableContainerLookup.GlobalSkinnableContainers skinnableTarget in Enum.GetValues()) { string filename = $"{skinnableTarget}.json"; @@ -187,7 +187,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) case SkinnableSprite.SpriteComponentLookup sprite: return this.GetAnimation(sprite.LookupName, false, false, maxSize: sprite.MaxSize); - case SkinComponentsContainerLookup containerLookup: + case GlobalSkinnableContainerLookup containerLookup: // It is important to return null if the user has not configured this yet. // This allows skin transformers the opportunity to provide default components. @@ -206,7 +206,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) #region Deserialisation & Migration - private SkinLayoutInfo? parseLayoutInfo(string jsonContent, SkinComponentsContainerLookup.TargetArea target) + private SkinLayoutInfo? parseLayoutInfo(string jsonContent, GlobalSkinnableContainerLookup.GlobalSkinnableContainers target) { SkinLayoutInfo? layout = null; @@ -245,7 +245,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) return layout; } - private void applyMigration(SkinLayoutInfo layout, SkinComponentsContainerLookup.TargetArea target, int version) + private void applyMigration(SkinLayoutInfo layout, GlobalSkinnableContainerLookup.GlobalSkinnableContainers target, int version) { switch (version) { @@ -253,7 +253,7 @@ private void applyMigration(SkinLayoutInfo layout, SkinComponentsContainerLookup { // Combo counters were moved out of the global HUD components into per-ruleset. // This is to allow some rulesets to customise further (ie. mania and catch moving the combo to within their play area). - if (target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents || + if (target != GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents || !layout.TryGetDrawableInfo(null, out var globalHUDComponents) || resources == null) break; diff --git a/osu.Game/Skinning/SkinnableContainer.cs b/osu.Game/Skinning/SkinnableContainer.cs index d2d4fac76654..c58992c5410f 100644 --- a/osu.Game/Skinning/SkinnableContainer.cs +++ b/osu.Game/Skinning/SkinnableContainer.cs @@ -26,7 +26,7 @@ public partial class SkinnableContainer : SkinReloadableDrawable, ISerialisableD /// /// The lookup criteria which will be used to retrieve components from the active skin. /// - public SkinComponentsContainerLookup Lookup { get; } + public GlobalSkinnableContainerLookup Lookup { get; } public IBindableList Components => components; @@ -38,7 +38,7 @@ public partial class SkinnableContainer : SkinReloadableDrawable, ISerialisableD private CancellationTokenSource? cancellationSource; - public SkinnableContainer(SkinComponentsContainerLookup lookup) + public SkinnableContainer(GlobalSkinnableContainerLookup lookup) { Lookup = lookup; } diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 7971aee794b3..8e694b4c3f9a 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -66,7 +66,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) switch (lookup) { - case SkinComponentsContainerLookup containerLookup: + case GlobalSkinnableContainerLookup containerLookup: if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; @@ -76,7 +76,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) switch (containerLookup.Target) { - case SkinComponentsContainerLookup.TargetArea.SongSelect: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => { // do stuff when we need to. @@ -84,7 +84,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) return songSelectComponents; - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); From 36b4013fa64857c23c70ab8f591bc2cc6b18c44f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 18:42:38 +0900 Subject: [PATCH 386/579] Rename `GameplaySkinComponentLookup` -> `SkinComponentLookup` --- .../CatchSkinComponentLookup.cs | 2 +- .../ManiaSkinComponentLookup.cs | 2 +- .../Argon/ManiaArgonSkinTransformer.cs | 2 +- .../Legacy/ManiaLegacySkinTransformer.cs | 2 +- .../OsuSkinComponentLookup.cs | 2 +- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 2 +- .../Default/OsuTrianglesSkinTransformer.cs | 2 +- .../Argon/TaikoArgonSkinTransformer.cs | 2 +- .../Legacy/TaikoLegacySkinTransformer.cs | 2 +- .../TaikoSkinComponentLookup.cs | 2 +- .../Rulesets/Judgements/DrawableJudgement.cs | 2 +- .../Skinning/GameplaySkinComponentLookup.cs | 28 ------------------- osu.Game/Skinning/ISkinComponentLookup.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/SkinComponentLookup.cs | 22 +++++++++++++++ 15 files changed, 35 insertions(+), 41 deletions(-) delete mode 100644 osu.Game/Skinning/GameplaySkinComponentLookup.cs create mode 100644 osu.Game/Skinning/SkinComponentLookup.cs diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs b/osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs index 596b102ac575..7f91d2990b5c 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs @@ -5,7 +5,7 @@ namespace osu.Game.Rulesets.Catch { - public class CatchSkinComponentLookup : GameplaySkinComponentLookup + public class CatchSkinComponentLookup : SkinComponentLookup { public CatchSkinComponentLookup(CatchSkinComponents component) : base(component) diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs index 046d1c5b3490..f3613eff992f 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs @@ -5,7 +5,7 @@ namespace osu.Game.Rulesets.Mania { - public class ManiaSkinComponentLookup : GameplaySkinComponentLookup + public class ManiaSkinComponentLookup : SkinComponentLookup { /// /// Creates a new . diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index f80cb3a88ad7..d13f0ca21bf9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -59,7 +59,7 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) return null; - case GameplaySkinComponentLookup resultComponent: + case SkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) return Drawable.Empty(); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 20017a78a297..c9fb55e9cedd 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -114,7 +114,7 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) return null; - case GameplaySkinComponentLookup resultComponent: + case SkinComponentLookup resultComponent: return getResult(resultComponent.Component); case ManiaSkinComponentLookup maniaComponent: diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs b/osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs index 3b3653e1ba1c..86a68c799f51 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs @@ -5,7 +5,7 @@ namespace osu.Game.Rulesets.Osu { - public class OsuSkinComponentLookup : GameplaySkinComponentLookup + public class OsuSkinComponentLookup : SkinComponentLookup { public OsuSkinComponentLookup(OsuSkinComponents component) : base(component) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index ec63e1194d3b..9f6f65c2068d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -18,7 +18,7 @@ public OsuArgonSkinTransformer(ISkin skin) { switch (lookup) { - case GameplaySkinComponentLookup resultComponent: + case SkinComponentLookup resultComponent: HitResult result = resultComponent.Component; // This should eventually be moved to a skin setting, when supported. diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs index 7a4c768aa25f..ef8cb12286e9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs @@ -18,7 +18,7 @@ public OsuTrianglesSkinTransformer(ISkin skin) { switch (lookup) { - case GameplaySkinComponentLookup resultComponent: + case SkinComponentLookup resultComponent: HitResult result = resultComponent.Component; switch (result) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs index 973b4a91ff25..bfc9e8648dc2 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs @@ -18,7 +18,7 @@ public TaikoArgonSkinTransformer(ISkin skin) { switch (lookup) { - case GameplaySkinComponentLookup resultComponent: + case SkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) return Drawable.Empty(); diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 894b91e9cebf..5bdb824f1c1c 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -29,7 +29,7 @@ public TaikoLegacySkinTransformer(ISkin skin) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is GameplaySkinComponentLookup) + if (lookup is SkinComponentLookup) { // if a taiko skin is providing explosion sprites, hide the judgements completely if (hasExplosion.Value) diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs index 8841c3d3cade..2fa4d3c9cb1b 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs @@ -5,7 +5,7 @@ namespace osu.Game.Rulesets.Taiko { - public class TaikoSkinComponentLookup : GameplaySkinComponentLookup + public class TaikoSkinComponentLookup : SkinComponentLookup { public TaikoSkinComponentLookup(TaikoSkinComponents component) : base(component) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 8c326ecf4946..3e70f52ee7ce 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -163,7 +163,7 @@ private void prepareDrawables() if (JudgementBody != null) RemoveInternal(JudgementBody, true); - AddInternal(JudgementBody = new SkinnableDrawable(new GameplaySkinComponentLookup(type), _ => + AddInternal(JudgementBody = new SkinnableDrawable(new SkinComponentLookup(type), _ => CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling)); JudgementBody.OnSkinChanged += () => diff --git a/osu.Game/Skinning/GameplaySkinComponentLookup.cs b/osu.Game/Skinning/GameplaySkinComponentLookup.cs deleted file mode 100644 index c317a17e2137..000000000000 --- a/osu.Game/Skinning/GameplaySkinComponentLookup.cs +++ /dev/null @@ -1,28 +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 System; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Skinning -{ - /// - /// 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 - /// (see 's usage for ) where ruleset-agnostic elements are required. - /// - /// An enum lookup type. - public class GameplaySkinComponentLookup : ISkinComponentLookup - where T : Enum - { - public readonly T Component; - - public GameplaySkinComponentLookup(T component) - { - Component = component; - } - } -} diff --git a/osu.Game/Skinning/ISkinComponentLookup.cs b/osu.Game/Skinning/ISkinComponentLookup.cs index 25ee086707ca..af2b512331b3 100644 --- a/osu.Game/Skinning/ISkinComponentLookup.cs +++ b/osu.Game/Skinning/ISkinComponentLookup.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning /// to scope particular lookup variations. Using this, a ruleset or skin implementation could make its own lookup /// type to scope away from more global contexts. /// - /// More commonly, a ruleset could make use of to do a simple lookup based on + /// More commonly, a ruleset could make use of to do a simple lookup based on /// a provided enum. /// public interface ISkinComponentLookup diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d9da208a7b1f..078bef9d0dcb 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -426,7 +426,7 @@ protected override void ParseConfigurationStream(Stream stream) return null; - case GameplaySkinComponentLookup resultComponent: + case SkinComponentLookup resultComponent: // kind of wasteful that we throw this away, but should do for now. if (getJudgementAnimation(resultComponent.Component) != null) diff --git a/osu.Game/Skinning/SkinComponentLookup.cs b/osu.Game/Skinning/SkinComponentLookup.cs new file mode 100644 index 000000000000..4da6bb0c087f --- /dev/null +++ b/osu.Game/Skinning/SkinComponentLookup.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Skinning +{ + /// + /// A lookup type intended for use for skinnable components. + /// + /// An enum lookup type. + public class SkinComponentLookup : ISkinComponentLookup + where T : Enum + { + public readonly T Component; + + public SkinComponentLookup(T component) + { + Component = component; + } + } +} From 9a21174582a53c5e77fd423c678002b2b0b0e9a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 18:45:44 +0900 Subject: [PATCH 387/579] Move `GlobalSkinnableContainers` to global scope --- .../Legacy/CatchLegacySkinTransformer.cs | 2 +- .../Argon/ManiaArgonSkinTransformer.cs | 2 +- .../Legacy/ManiaLegacySkinTransformer.cs | 2 +- .../Legacy/OsuLegacySkinTransformer.cs | 2 +- .../Skins/SkinDeserialisationTest.cs | 20 ++++++++--------- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 4 ++-- .../Visual/Gameplay/TestSceneSkinEditor.cs | 4 ++-- .../TestSceneSkinEditorComponentsList.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 6 ++--- osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Skinning/ArgonSkin.cs | 4 ++-- .../GlobalSkinnableContainerLookup.cs | 16 -------------- .../Skinning/GlobalSkinnableContainers.cs | 22 +++++++++++++++++++ osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/Skin.cs | 14 ++++++------ osu.Game/Skinning/TrianglesSkin.cs | 4 ++-- 17 files changed, 58 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Skinning/GlobalSkinnableContainers.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index ab0420554ee9..61ef1de2b9f1 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -46,7 +46,7 @@ public CatchLegacySkinTransformer(ISkin skin) // Our own ruleset components default. switch (containerLookup.Target) { - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: + case GlobalSkinnableContainers.MainHUDComponents: // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. return new DefaultSkinComponentsContainer(container => { diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index d13f0ca21bf9..4d9798b26474 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -39,7 +39,7 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) switch (containerLookup.Target) { - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: + case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => { var combo = container.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index c9fb55e9cedd..2a79d58f224a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -95,7 +95,7 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) switch (containerLookup.Target) { - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: + case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => { var combo = container.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 6609a84be4d2..12dac186943d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -60,7 +60,7 @@ public OsuLegacySkinTransformer(ISkin skin) // Our own ruleset components default. switch (containerLookup.Target) { - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: + case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => { var keyCounter = container.OfType().FirstOrDefault(); diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index ad01a057ad43..82b46ee75f71 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -107,7 +107,7 @@ public void TestDeserialiseModifiedDefault() var skin = new TestSkin(new SkinInfo(), null, storage); Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2)); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9)); } } @@ -120,8 +120,8 @@ public void TestDeserialiseModifiedArgon() var skin = new TestSkin(new SkinInfo(), null, storage); Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2)); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(10)); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(PlayerName))); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(10)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(PlayerName))); } } @@ -134,10 +134,10 @@ public void TestDeserialiseModifiedClassic() var skin = new TestSkin(new SkinInfo(), null, storage); Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2)); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6)); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1)); - var skinnableInfo = skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.SongSelect].AllDrawables.First(); + var skinnableInfo = skin.LayoutInfos[GlobalSkinnableContainers.SongSelect].AllDrawables.First(); Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite))); Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name")); @@ -148,10 +148,10 @@ public void TestDeserialiseModifiedClassic() using (var storage = new ZipArchiveReader(stream)) { var skin = new TestSkin(new SkinInfo(), null, storage); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8)); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter))); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter))); - Assert.That(skin.LayoutInfos[GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(LegacySongProgress))); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8)); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter))); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter))); + Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.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 1061f493d46d..5230cea7a583 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -38,7 +38,7 @@ 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(GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents, skinManager.CurrentSkin.Value)); + AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(GlobalSkinnableContainers.MainHUDComponents, skinManager.CurrentSkin.Value)); } protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSkin) @@ -53,7 +53,7 @@ protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSk }); } - protected bool AssertComponentsFromExpectedSource(GlobalSkinnableContainerLookup.GlobalSkinnableContainers target, ISkin expectedSource) + protected bool AssertComponentsFromExpectedSource(GlobalSkinnableContainers target, ISkin expectedSource) { var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Target == target); var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 9e53f86e3340..2a2bff218a3d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -378,10 +378,10 @@ public void TestCopyPaste() } private SkinnableContainer globalHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Target == GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null); + .Single(c => c.Lookup.Target == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null); private SkinnableContainer rulesetHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Target == GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset != null); + .Single(c => c.Lookup.Target == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset != null); [Test] public void TestMigrationArgon() diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index e4b6358600bf..b5fe6633b625 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -20,7 +20,7 @@ public partial class TestSceneSkinEditorComponentsList : SkinnableTestScene [Test] public void TestToggleEditor() { - var skinComponentsContainer = new SkinnableContainer(new GlobalSkinnableContainerLookup(GlobalSkinnableContainerLookup.GlobalSkinnableContainers.SongSelect)); + var skinComponentsContainer = new SkinnableContainer(new GlobalSkinnableContainerLookup(GlobalSkinnableContainers.SongSelect)); AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(skinComponentsContainer, null) { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 7bddef534c61..ac1b9ce34fa8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -109,7 +109,7 @@ protected override bool ShouldBeConsideredForInput(Drawable child) private readonly List hideTargets; /// - /// The container for skin components attached to + /// The container for skin components attached to /// internal readonly Drawable PlayfieldSkinLayer; @@ -132,7 +132,7 @@ public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList { // do stuff when we need to. @@ -111,7 +111,7 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) return songSelectComponents; - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: + case GlobalSkinnableContainers.MainHUDComponents: if (containerLookup.Ruleset != null) { return new Container diff --git a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs index 384b4aa23c1c..cac8c3bb2fc9 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.ComponentModel; using osu.Framework.Extensions; using osu.Game.Rulesets; @@ -58,20 +57,5 @@ public override int GetHashCode() { return HashCode.Combine((int)Target, Ruleset); } - - /// - /// Represents a particular area or part of a game screen whose layout can be customised using the skin editor. - /// - public enum GlobalSkinnableContainers - { - [Description("HUD")] - MainHUDComponents, - - [Description("Song select")] - SongSelect, - - [Description("Playfield")] - Playfield - } } } diff --git a/osu.Game/Skinning/GlobalSkinnableContainers.cs b/osu.Game/Skinning/GlobalSkinnableContainers.cs new file mode 100644 index 000000000000..02f915895f37 --- /dev/null +++ b/osu.Game/Skinning/GlobalSkinnableContainers.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Skinning +{ + /// + /// Represents a particular area or part of a game screen whose layout can be customised using the skin editor. + /// + public enum GlobalSkinnableContainers + { + [Description("HUD")] + MainHUDComponents, + + [Description("Song select")] + SongSelect, + + [Description("Playfield")] + Playfield + } +} diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 54e259a807e4..81dc79b25fe5 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -54,7 +54,7 @@ private static IResourceStore createRealmBackedStore(BeatmapInfo beatmap { switch (containerLookup.Target) { - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: + case GlobalSkinnableContainers.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 078bef9d0dcb..0085bf62ac33 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -364,7 +364,7 @@ protected override void ParseConfigurationStream(Stream stream) switch (containerLookup.Target) { - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: + case GlobalSkinnableContainers.MainHUDComponents: if (containerLookup.Ruleset != null) { return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 581c47402f1e..449a30c022bc 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -43,10 +43,10 @@ public abstract class Skin : IDisposable, ISkin public SkinConfiguration Configuration { get; set; } - public IDictionary LayoutInfos => layoutInfos; + public IDictionary LayoutInfos => layoutInfos; - private readonly Dictionary layoutInfos = - new Dictionary(); + private readonly Dictionary layoutInfos = + new Dictionary(); public abstract ISample? GetSample(ISampleInfo sampleInfo); @@ -123,7 +123,7 @@ protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStor } // skininfo files may be null for default skin. - foreach (GlobalSkinnableContainerLookup.GlobalSkinnableContainers skinnableTarget in Enum.GetValues()) + foreach (GlobalSkinnableContainers skinnableTarget in Enum.GetValues()) { string filename = $"{skinnableTarget}.json"; @@ -206,7 +206,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) #region Deserialisation & Migration - private SkinLayoutInfo? parseLayoutInfo(string jsonContent, GlobalSkinnableContainerLookup.GlobalSkinnableContainers target) + private SkinLayoutInfo? parseLayoutInfo(string jsonContent, GlobalSkinnableContainers target) { SkinLayoutInfo? layout = null; @@ -245,7 +245,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) return layout; } - private void applyMigration(SkinLayoutInfo layout, GlobalSkinnableContainerLookup.GlobalSkinnableContainers target, int version) + private void applyMigration(SkinLayoutInfo layout, GlobalSkinnableContainers target, int version) { switch (version) { @@ -253,7 +253,7 @@ private void applyMigration(SkinLayoutInfo layout, GlobalSkinnableContainerLooku { // Combo counters were moved out of the global HUD components into per-ruleset. // This is to allow some rulesets to customise further (ie. mania and catch moving the combo to within their play area). - if (target != GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents || + if (target != GlobalSkinnableContainers.MainHUDComponents || !layout.TryGetDrawableInfo(null, out var globalHUDComponents) || resources == null) break; diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 8e694b4c3f9a..b0cb54a6f979 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -76,7 +76,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) switch (containerLookup.Target) { - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.SongSelect: + case GlobalSkinnableContainers.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => { // do stuff when we need to. @@ -84,7 +84,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) return songSelectComponents; - case GlobalSkinnableContainerLookup.GlobalSkinnableContainers.MainHUDComponents: + case GlobalSkinnableContainers.MainHUDComponents: var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); From b57b8168a62c8ab481b4f2d790cf6a0213f08b89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 19:00:15 +0900 Subject: [PATCH 388/579] Rename `Target` lookup to `Component` --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 +- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 2 +- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 2 +- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 2 +- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- .../Visual/Gameplay/TestSceneSkinEditor.cs | 4 ++-- osu.Game/Skinning/ArgonSkin.cs | 2 +- .../Skinning/GlobalSkinnableContainerLookup.cs | 14 +++++++------- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/Skin.cs | 8 ++++---- osu.Game/Skinning/TrianglesSkin.cs | 2 +- 12 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 61ef1de2b9f1..a62a71200147 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -44,7 +44,7 @@ public CatchLegacySkinTransformer(ISkin skin) return null; // Our own ruleset components default. - switch (containerLookup.Target) + switch (containerLookup.Component) { case GlobalSkinnableContainers.MainHUDComponents: // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 4d9798b26474..2c361df8b1c3 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -37,7 +37,7 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; - switch (containerLookup.Target) + switch (containerLookup.Component) { case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 2a79d58f224a..895f5a7cc153 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -93,7 +93,7 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) if (!IsProvidingLegacyResources) return null; - switch (containerLookup.Target) + switch (containerLookup.Component) { case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 12dac186943d..26708f668697 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -58,7 +58,7 @@ public OsuLegacySkinTransformer(ISkin skin) return null; // Our own ruleset components default. - switch (containerLookup.Target) + switch (containerLookup.Component) { case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 5230cea7a583..9a4f084d104b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -55,7 +55,7 @@ protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSk protected bool AssertComponentsFromExpectedSource(GlobalSkinnableContainers target, ISkin expectedSource) { - var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Target == target); + var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Component == target); var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer); if (actualComponentsContainer == null) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 2a2bff218a3d..4dca8c90017c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -378,10 +378,10 @@ public void TestCopyPaste() } private SkinnableContainer globalHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Target == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null); + .Single(c => c.Lookup.Component == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null); private SkinnableContainer rulesetHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Target == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset != null); + .Single(c => c.Lookup.Component == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset != null); [Test] public void TestMigrationArgon() diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 74ab3d885e58..2489013c1ea8 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -101,7 +101,7 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; - switch (containerLookup.Target) + switch (containerLookup.Component) { case GlobalSkinnableContainers.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => diff --git a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs index cac8c3bb2fc9..524d99197af7 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs @@ -15,7 +15,7 @@ public class GlobalSkinnableContainerLookup : ISkinComponentLookup, IEquatable /// The target area / layer of the game for which skin components will be returned. /// - public readonly GlobalSkinnableContainers Target; + public readonly GlobalSkinnableContainers Component; /// /// The ruleset for which skin components should be returned. @@ -23,17 +23,17 @@ public class GlobalSkinnableContainerLookup : ISkinComponentLookup, IEquatable public readonly RulesetInfo? Ruleset; - public GlobalSkinnableContainerLookup(GlobalSkinnableContainers target, RulesetInfo? ruleset = null) + public GlobalSkinnableContainerLookup(GlobalSkinnableContainers component, RulesetInfo? ruleset = null) { - Target = target; + Component = component; Ruleset = ruleset; } public override string ToString() { - if (Ruleset == null) return Target.GetDescription(); + if (Ruleset == null) return Component.GetDescription(); - return $"{Target.GetDescription()} (\"{Ruleset.Name}\" only)"; + return $"{Component.GetDescription()} (\"{Ruleset.Name}\" only)"; } public bool Equals(GlobalSkinnableContainerLookup? other) @@ -41,7 +41,7 @@ public bool Equals(GlobalSkinnableContainerLookup? other) if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Target == other.Target && (ReferenceEquals(Ruleset, other.Ruleset) || Ruleset?.Equals(other.Ruleset) == true); + return Component == other.Component && (ReferenceEquals(Ruleset, other.Ruleset) || Ruleset?.Equals(other.Ruleset) == true); } public override bool Equals(object? obj) @@ -55,7 +55,7 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return HashCode.Combine((int)Target, Ruleset); + return HashCode.Combine((int)Component, Ruleset); } } } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 81dc79b25fe5..c8a93f418f35 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -52,7 +52,7 @@ private static IResourceStore createRealmBackedStore(BeatmapInfo beatmap { if (lookup is GlobalSkinnableContainerLookup containerLookup) { - switch (containerLookup.Target) + switch (containerLookup.Component) { case GlobalSkinnableContainers.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 0085bf62ac33..16d9cf391cef 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -362,7 +362,7 @@ protected override void ParseConfigurationStream(Stream stream) if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; - switch (containerLookup.Target) + switch (containerLookup.Component) { case GlobalSkinnableContainers.MainHUDComponents: if (containerLookup.Ruleset != null) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 449a30c022bc..04a7fd53f769 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -164,7 +164,7 @@ protected virtual void ParseConfigurationStream(Stream stream) /// The target container to reset. public void ResetDrawableTarget(SkinnableContainer targetContainer) { - LayoutInfos.Remove(targetContainer.Lookup.Target); + LayoutInfos.Remove(targetContainer.Lookup.Component); } /// @@ -173,8 +173,8 @@ public void ResetDrawableTarget(SkinnableContainer targetContainer) /// The target container to serialise to this skin. public void UpdateDrawableTarget(SkinnableContainer targetContainer) { - if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Target, out var layoutInfo)) - layoutInfos[targetContainer.Lookup.Target] = layoutInfo = new SkinLayoutInfo(); + if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Component, out var layoutInfo)) + layoutInfos[targetContainer.Lookup.Component] = layoutInfo = new SkinLayoutInfo(); layoutInfo.Update(targetContainer.Lookup.Ruleset, ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray()); } @@ -191,7 +191,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) // It is important to return null if the user has not configured this yet. // This allows skin transformers the opportunity to provide default components. - if (!LayoutInfos.TryGetValue(containerLookup.Target, out var layoutInfo)) return null; + if (!LayoutInfos.TryGetValue(containerLookup.Component, out var layoutInfo)) return null; if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null; return new UserConfiguredLayoutContainer diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index b0cb54a6f979..c0d327a082d8 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -74,7 +74,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) if (containerLookup.Ruleset != null) return null; - switch (containerLookup.Target) + switch (containerLookup.Component) { case GlobalSkinnableContainers.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => From 1435fe24ae8076baa494652439785ab318009ea3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 17:14:35 +0900 Subject: [PATCH 389/579] Remove requirement of `base` calls to ensure user skin container layouts are retrieved --- .../Legacy/CatchLegacySkinTransformer.cs | 4 --- .../Legacy/ManiaLegacySkinTransformer.cs | 4 --- .../Legacy/OsuLegacySkinTransformer.cs | 4 --- osu.Game/Skinning/Skin.cs | 27 +++++++++++-------- osu.Game/Skinning/SkinnableContainer.cs | 5 +++- osu.Game/Skinning/UserSkinComponentLookup.cs | 18 +++++++++++++ 6 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 osu.Game/Skinning/UserSkinComponentLookup.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index a62a71200147..e64dcd4e7599 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -35,10 +35,6 @@ public CatchLegacySkinTransformer(ISkin skin) if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); - // Skin has configuration. - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) - return d; - // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). if (!IsProvidingLegacyResources) return null; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 895f5a7cc153..3372cb70db68 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -85,10 +85,6 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); - // Skin has configuration. - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) - return d; - // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). if (!IsProvidingLegacyResources) return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 26708f668697..afccdcc3ac23 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -49,10 +49,6 @@ public OsuLegacySkinTransformer(ISkin skin) if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); - // Skin has configuration. - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) - return d; - // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). if (!IsProvidingLegacyResources) return null; diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 04a7fd53f769..694aaf882a50 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -187,18 +187,23 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) case SkinnableSprite.SpriteComponentLookup sprite: return this.GetAnimation(sprite.LookupName, false, false, maxSize: sprite.MaxSize); - case GlobalSkinnableContainerLookup containerLookup: - - // It is important to return null if the user has not configured this yet. - // This allows skin transformers the opportunity to provide default components. - if (!LayoutInfos.TryGetValue(containerLookup.Component, out var layoutInfo)) return null; - if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null; - - return new UserConfiguredLayoutContainer + case UserSkinComponentLookup userLookup: + switch (userLookup.Component) { - RelativeSizeAxes = Axes.Both, - ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance()) - }; + case GlobalSkinnableContainerLookup containerLookup: + // It is important to return null if the user has not configured this yet. + // This allows skin transformers the opportunity to provide default components. + if (!LayoutInfos.TryGetValue(containerLookup.Component, out var layoutInfo)) return null; + if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null; + + return new UserConfiguredLayoutContainer + { + RelativeSizeAxes = Axes.Both, + ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance()) + }; + } + + break; } return null; diff --git a/osu.Game/Skinning/SkinnableContainer.cs b/osu.Game/Skinning/SkinnableContainer.cs index c58992c5410f..aad95ca779c2 100644 --- a/osu.Game/Skinning/SkinnableContainer.cs +++ b/osu.Game/Skinning/SkinnableContainer.cs @@ -43,7 +43,10 @@ public SkinnableContainer(GlobalSkinnableContainerLookup lookup) Lookup = lookup; } - public void Reload() => Reload(CurrentSkin.GetDrawableComponent(Lookup) as Container); + public void Reload() => Reload(( + CurrentSkin.GetDrawableComponent(new UserSkinComponentLookup(Lookup)) + ?? CurrentSkin.GetDrawableComponent(Lookup)) + as Container); public void Reload(Container? componentsContainer) { diff --git a/osu.Game/Skinning/UserSkinComponentLookup.cs b/osu.Game/Skinning/UserSkinComponentLookup.cs new file mode 100644 index 000000000000..1ecdc96b38c9 --- /dev/null +++ b/osu.Game/Skinning/UserSkinComponentLookup.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Skinning +{ + /// + /// A lookup class which is only for internal use, and explicitly to get a user-level configuration. + /// + internal class UserSkinComponentLookup : ISkinComponentLookup + { + public readonly ISkinComponentLookup Component; + + public UserSkinComponentLookup(ISkinComponentLookup component) + { + Component = component; + } + } +} From 58552e97680175ca74e2e7c2f0b0fcad2a711ecb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 19:18:41 +0900 Subject: [PATCH 390/579] Add missing user ruleset to link copying for beatmap panels --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 66d1480fdcd3..359e0f6c783e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -295,7 +295,7 @@ public MenuItem[] ContextMenuItems items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - if (beatmapInfo.GetOnlineURL(api) is string url) + if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url) items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); if (hideRequested != null) From 46d55d5e61356b8e7a1771e9e9c72fb014713324 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2024 20:13:24 +0900 Subject: [PATCH 391/579] Remove remaining early `base` lookup calls which were missed --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 4 ---- osu.Game/Skinning/ArgonSkin.cs | 4 ---- osu.Game/Skinning/LegacySkin.cs | 3 --- osu.Game/Skinning/Skin.cs | 3 ++- osu.Game/Skinning/TrianglesSkin.cs | 3 --- .../Skinning/UserConfiguredLayoutContainer.cs | 15 --------------- 6 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 osu.Game/Skinning/UserConfiguredLayoutContainer.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 2c361df8b1c3..8707246402c2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -33,10 +33,6 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); - // Skin has configuration. - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) - return d; - switch (containerLookup.Component) { case GlobalSkinnableContainers.MainHUDComponents: diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 2489013c1ea8..6baba02d296d 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -97,10 +97,6 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) switch (lookup) { case GlobalSkinnableContainerLookup containerLookup: - - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - switch (containerLookup.Component) { case GlobalSkinnableContainers.SongSelect: diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 16d9cf391cef..8706f24e618b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -359,9 +359,6 @@ protected override void ParseConfigurationStream(Stream stream) switch (lookup) { case GlobalSkinnableContainerLookup containerLookup: - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - switch (containerLookup.Component) { case GlobalSkinnableContainers.MainHUDComponents: diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 694aaf882a50..4c7dda50a992 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -14,6 +14,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -196,7 +197,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) if (!LayoutInfos.TryGetValue(containerLookup.Component, out var layoutInfo)) return null; if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null; - return new UserConfiguredLayoutContainer + return new Container { RelativeSizeAxes = Axes.Both, ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance()) diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index c0d327a082d8..ca0653ee12de 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -67,9 +67,6 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) switch (lookup) { case GlobalSkinnableContainerLookup containerLookup: - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - // Only handle global level defaults for now. if (containerLookup.Ruleset != null) return null; diff --git a/osu.Game/Skinning/UserConfiguredLayoutContainer.cs b/osu.Game/Skinning/UserConfiguredLayoutContainer.cs deleted file mode 100644 index 1b5a27b53be4..000000000000 --- a/osu.Game/Skinning/UserConfiguredLayoutContainer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Skinning -{ - /// - /// This signifies that a call resolved a configuration created - /// by a user in their skin. Generally this should be given priority over any local defaults or overrides. - /// - public partial class UserConfiguredLayoutContainer : Container - { - } -} From 0db068e423024d35bb4e2145d134e8f7dc7e2988 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 22 Aug 2024 19:15:53 +0200 Subject: [PATCH 392/579] allow repeating on seek actions --- osu.Game/Screens/Edit/Editor.cs | 41 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2933c89cd881..355d72443462 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -714,6 +714,26 @@ protected override bool OnScroll(ScrollEvent e) public bool OnPressed(KeyBindingPressEvent e) { + // Repeatable actions + switch (e.Action) + { + case GlobalAction.EditorSeekToPreviousHitObject: + seekHitObject(-1); + return true; + + case GlobalAction.EditorSeekToNextHitObject: + seekHitObject(1); + return true; + + case GlobalAction.EditorSeekToPreviousSamplePoint: + seekSamplePoint(-1); + return true; + + case GlobalAction.EditorSeekToNextSamplePoint: + seekSamplePoint(1); + return true; + } + if (e.Repeat) return false; @@ -751,26 +771,9 @@ public bool OnPressed(KeyBindingPressEvent e) case GlobalAction.EditorTestGameplay: bottomBar.TestGameplayButton.TriggerClick(); return true; - - case GlobalAction.EditorSeekToPreviousHitObject: - seekHitObject(-1); - return true; - - case GlobalAction.EditorSeekToNextHitObject: - seekHitObject(1); - return true; - - case GlobalAction.EditorSeekToPreviousSamplePoint: - seekSamplePoint(-1); - return true; - - case GlobalAction.EditorSeekToNextSamplePoint: - seekSamplePoint(1); - return true; - - default: - return false; } + + return false; } public void OnReleased(KeyBindingReleaseEvent e) From adbdb39e9f57ce6e548ddba58798f797e9430de6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 22 Aug 2024 19:18:38 +0200 Subject: [PATCH 393/579] move public member to top of file --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 355d72443462..6b8ea7e97e09 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -225,6 +225,9 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl /// public Bindable ComposerFocusMode { get; } = new Bindable(); + [CanBeNull] + public event Action ShowSampleEditPopoverRequested; + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -1107,9 +1110,6 @@ private void seekHitObject(int direction) clock.SeekSmoothlyTo(found.StartTime); } - [CanBeNull] - public event Action ShowSampleEditPopoverRequested; - private void seekSamplePoint(int direction) { double currentTime = clock.CurrentTimeAccurate; From 998b5fdc12122a538dadbd3b7afcda868eb3bdda Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 22 Aug 2024 19:53:34 +0200 Subject: [PATCH 394/579] Add property EditorShowScrollSpeed to Ruleset --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 ++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 ++ osu.Game/Rulesets/Ruleset.cs | 5 +++++ osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 2 -- osu.Game/Rulesets/UI/Scrolling/IDrawableScrollingRuleset.cs | 4 ---- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 6 +----- osu.Game/Screens/Edit/Timing/EffectSection.cs | 5 +---- .../Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs | 6 +----- 8 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 3edc23a8b750..7eaf4f2b1869 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -254,5 +254,7 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } + + public override bool EditorShowScrollSpeed => false; } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7042ad0cd440..be48ef9acc47 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -359,5 +359,7 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } + + public override bool EditorShowScrollSpeed => false; } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index fb0e225c94f2..5af1fd386c3a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -401,5 +401,10 @@ public virtual IEnumerable CreateEditorSetupSections() => new DifficultySection(), new ColoursSection(), ]; + + /// + /// Can be overridden to avoid showing scroll speed changes in the editor. + /// + public virtual bool EditorShowScrollSpeed => true; } } diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index d23658ac3304..ba3a9bd48341 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -64,8 +64,6 @@ public abstract partial class DrawableScrollingRuleset : DrawableRulese MaxValue = time_span_max }; - ScrollVisualisationMethod IDrawableScrollingRuleset.VisualisationMethod => VisualisationMethod; - /// /// Whether the player can change . /// diff --git a/osu.Game/Rulesets/UI/Scrolling/IDrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/IDrawableScrollingRuleset.cs index b348a220093f..27531492d6d6 100644 --- a/osu.Game/Rulesets/UI/Scrolling/IDrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/IDrawableScrollingRuleset.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. -using osu.Game.Configuration; - namespace osu.Game.Rulesets.UI.Scrolling { /// @@ -10,8 +8,6 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public interface IDrawableScrollingRuleset { - ScrollVisualisationMethod VisualisationMethod { get; } - IScrollingInfo ScrollingInfo { get; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index e3f90558c504..f1a8dc5e352b 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -9,10 +9,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Graphics; -using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { @@ -81,9 +79,7 @@ private void refreshDisplay() { ClearInternal(); - var drawableRuleset = beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateDrawableRulesetWith(beatmap.PlayableBeatmap); - - if (drawableRuleset is IDrawableScrollingRuleset scrollingRuleset && scrollingRuleset.VisualisationMethod != ScrollVisualisationMethod.Constant) + if (beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed) AddInternal(new ControlPointVisualisation(effect)); if (!kiai.Value) diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index f321f7eeb0a3..a4b9f37dffd6 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -5,9 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Screens.Edit.Timing { @@ -38,8 +36,7 @@ protected override void LoadComplete() kiai.Current.BindValueChanged(_ => saveChanges()); scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges()); - var drawableRuleset = Beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateDrawableRulesetWith(Beatmap.PlayableBeatmap); - if (drawableRuleset is not IDrawableScrollingRuleset scrollingRuleset || scrollingRuleset.VisualisationMethod == ScrollVisualisationMethod.Constant) + if (!Beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed) scrollSpeedSlider.Hide(); void saveChanges() diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs index 253bfdd73a62..87ee675e7f0e 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs @@ -5,8 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Configuration; -using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Screens.Edit.Timing.RowAttributes { @@ -42,9 +40,7 @@ private void load() kiaiModeBubble = new AttributeText(Point) { Text = "kiai" }, }); - var drawableRuleset = Beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateDrawableRulesetWith(Beatmap.PlayableBeatmap); - - if (drawableRuleset is not IDrawableScrollingRuleset scrollingRuleset || scrollingRuleset.VisualisationMethod == ScrollVisualisationMethod.Constant) + if (!Beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed) { text.Hide(); progressBar.Hide(); From ad8e7f1897fbae3c76e8438bd2263217f67819fb Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 22 Aug 2024 20:17:09 +0200 Subject: [PATCH 395/579] Fix scroll speed visualisation missing on start kiai effect points They were being drawn far offscreen because the width of the kiai multiplied with the X coordinate of the scroll speed vis. --- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index f1a8dc5e352b..b4e6d1ece2f9 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -80,7 +80,13 @@ private void refreshDisplay() ClearInternal(); if (beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed) - AddInternal(new ControlPointVisualisation(effect)); + { + AddInternal(new ControlPointVisualisation(effect) + { + // importantly, override the x position being set since we do that in the GroupVisualisation parent drawable. + X = 0, + }); + } if (!kiai.Value) return; From 48cfd77ee8d0ef359db019855ce6653103b23cef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Aug 2024 14:48:50 +0900 Subject: [PATCH 396/579] `Component` -> `Lookup` --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 +- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 2 +- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 2 +- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 2 +- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- .../Visual/Gameplay/TestSceneSkinEditor.cs | 4 ++-- osu.Game/Skinning/ArgonSkin.cs | 2 +- .../Skinning/GlobalSkinnableContainerLookup.cs | 14 +++++++------- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/Skin.cs | 8 ++++---- osu.Game/Skinning/TrianglesSkin.cs | 2 +- 12 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index e64dcd4e7599..69efb7fbca84 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -40,7 +40,7 @@ public CatchLegacySkinTransformer(ISkin skin) return null; // Our own ruleset components default. - switch (containerLookup.Component) + switch (containerLookup.Lookup) { case GlobalSkinnableContainers.MainHUDComponents: // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 8707246402c2..afccb2e56829 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -33,7 +33,7 @@ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); - switch (containerLookup.Component) + switch (containerLookup.Lookup) { case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 3372cb70db68..cb42b2b62aba 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -89,7 +89,7 @@ public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) if (!IsProvidingLegacyResources) return null; - switch (containerLookup.Component) + switch (containerLookup.Lookup) { case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index afccdcc3ac23..636a9ecb21b1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -54,7 +54,7 @@ public OsuLegacySkinTransformer(ISkin skin) return null; // Our own ruleset components default. - switch (containerLookup.Component) + switch (containerLookup.Lookup) { case GlobalSkinnableContainers.MainHUDComponents: return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 9a4f084d104b..5ec32f318cee 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -55,7 +55,7 @@ protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSk protected bool AssertComponentsFromExpectedSource(GlobalSkinnableContainers target, ISkin expectedSource) { - var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Component == target); + var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Lookup == target); var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer); if (actualComponentsContainer == null) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 4dca8c90017c..3a7bc0530010 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -378,10 +378,10 @@ public void TestCopyPaste() } private SkinnableContainer globalHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Component == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null); + .Single(c => c.Lookup.Lookup == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null); private SkinnableContainer rulesetHUDTarget => Player.ChildrenOfType() - .Single(c => c.Lookup.Component == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset != null); + .Single(c => c.Lookup.Lookup == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset != null); [Test] public void TestMigrationArgon() diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6baba02d296d..771d10d73b0e 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -97,7 +97,7 @@ public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) switch (lookup) { case GlobalSkinnableContainerLookup containerLookup: - switch (containerLookup.Component) + switch (containerLookup.Lookup) { case GlobalSkinnableContainers.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => diff --git a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs index 524d99197af7..6d78981f0a67 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs @@ -15,7 +15,7 @@ public class GlobalSkinnableContainerLookup : ISkinComponentLookup, IEquatable /// The target area / layer of the game for which skin components will be returned. /// - public readonly GlobalSkinnableContainers Component; + public readonly GlobalSkinnableContainers Lookup; /// /// The ruleset for which skin components should be returned. @@ -23,17 +23,17 @@ public class GlobalSkinnableContainerLookup : ISkinComponentLookup, IEquatable public readonly RulesetInfo? Ruleset; - public GlobalSkinnableContainerLookup(GlobalSkinnableContainers component, RulesetInfo? ruleset = null) + public GlobalSkinnableContainerLookup(GlobalSkinnableContainers lookup, RulesetInfo? ruleset = null) { - Component = component; + Lookup = lookup; Ruleset = ruleset; } public override string ToString() { - if (Ruleset == null) return Component.GetDescription(); + if (Ruleset == null) return Lookup.GetDescription(); - return $"{Component.GetDescription()} (\"{Ruleset.Name}\" only)"; + return $"{Lookup.GetDescription()} (\"{Ruleset.Name}\" only)"; } public bool Equals(GlobalSkinnableContainerLookup? other) @@ -41,7 +41,7 @@ public bool Equals(GlobalSkinnableContainerLookup? other) if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Component == other.Component && (ReferenceEquals(Ruleset, other.Ruleset) || Ruleset?.Equals(other.Ruleset) == true); + return Lookup == other.Lookup && (ReferenceEquals(Ruleset, other.Ruleset) || Ruleset?.Equals(other.Ruleset) == true); } public override bool Equals(object? obj) @@ -55,7 +55,7 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return HashCode.Combine((int)Component, Ruleset); + return HashCode.Combine((int)Lookup, Ruleset); } } } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index c8a93f418f35..656c0e046f59 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -52,7 +52,7 @@ private static IResourceStore createRealmBackedStore(BeatmapInfo beatmap { if (lookup is GlobalSkinnableContainerLookup containerLookup) { - switch (containerLookup.Component) + switch (containerLookup.Lookup) { case GlobalSkinnableContainers.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 8706f24e618b..6faadfba9b54 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -359,7 +359,7 @@ protected override void ParseConfigurationStream(Stream stream) switch (lookup) { case GlobalSkinnableContainerLookup containerLookup: - switch (containerLookup.Component) + switch (containerLookup.Lookup) { case GlobalSkinnableContainers.MainHUDComponents: if (containerLookup.Ruleset != null) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 4c7dda50a992..2382253036a9 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -165,7 +165,7 @@ protected virtual void ParseConfigurationStream(Stream stream) /// The target container to reset. public void ResetDrawableTarget(SkinnableContainer targetContainer) { - LayoutInfos.Remove(targetContainer.Lookup.Component); + LayoutInfos.Remove(targetContainer.Lookup.Lookup); } /// @@ -174,8 +174,8 @@ public void ResetDrawableTarget(SkinnableContainer targetContainer) /// The target container to serialise to this skin. public void UpdateDrawableTarget(SkinnableContainer targetContainer) { - if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Component, out var layoutInfo)) - layoutInfos[targetContainer.Lookup.Component] = layoutInfo = new SkinLayoutInfo(); + if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Lookup, out var layoutInfo)) + layoutInfos[targetContainer.Lookup.Lookup] = layoutInfo = new SkinLayoutInfo(); layoutInfo.Update(targetContainer.Lookup.Ruleset, ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray()); } @@ -194,7 +194,7 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) case GlobalSkinnableContainerLookup containerLookup: // It is important to return null if the user has not configured this yet. // This allows skin transformers the opportunity to provide default components. - if (!LayoutInfos.TryGetValue(containerLookup.Component, out var layoutInfo)) return null; + if (!LayoutInfos.TryGetValue(containerLookup.Lookup, out var layoutInfo)) return null; if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null; return new Container diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index ca0653ee12de..d562fd325675 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -71,7 +71,7 @@ public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) if (containerLookup.Ruleset != null) return null; - switch (containerLookup.Component) + switch (containerLookup.Lookup) { case GlobalSkinnableContainers.SongSelect: var songSelectComponents = new DefaultSkinComponentsContainer(_ => From 2a479a84dce4d8d3840c8645118a82ac76f1be7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Aug 2024 18:21:31 +0900 Subject: [PATCH 397/579] Remove conditional inhibiting seek when beatmap change is not allowed In testing I can't find a reason for this to exist. Blaming back shows that it existed before we had `AllowTrackControl` and was likely being used as a stop-gap measure to achieve the same thing. It's existed since over 6 years ago. Let's give removing it a try to fix some usability concerns? Closes https://github.com/ppy/osu/issues/29563. --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 27c7cd0f49af..63efdd5381b6 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -115,7 +115,7 @@ public void SeekTo(double position) seekDelegate?.Cancel(); seekDelegate = Schedule(() => { - if (beatmap.Disabled || !AllowTrackControl.Value) + if (!AllowTrackControl.Value) return; CurrentTrack.Seek(position); From 1e39af8ac5f07c9992faf062572390d85ea7e981 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Aug 2024 19:49:15 +0900 Subject: [PATCH 398/579] Add a bit of logging around medal awarding Might help with https://github.com/ppy/osu/issues/29119. --- osu.Game/Overlays/MedalAnimation.cs | 7 ++++--- osu.Game/Overlays/MedalOverlay.cs | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MedalAnimation.cs b/osu.Game/Overlays/MedalAnimation.cs index 25776d50db6f..daceeedf47c1 100644 --- a/osu.Game/Overlays/MedalAnimation.cs +++ b/osu.Game/Overlays/MedalAnimation.cs @@ -30,7 +30,8 @@ public partial class MedalAnimation : VisibilityContainer private const float border_width = 5; - private readonly Medal medal; + public readonly Medal Medal; + private readonly Box background; private readonly Container backgroundStrip, particleContainer; private readonly BackgroundStrip leftStrip, rightStrip; @@ -44,7 +45,7 @@ public partial class MedalAnimation : VisibilityContainer public MedalAnimation(Medal medal) { - this.medal = medal; + Medal = medal; RelativeSizeAxes = Axes.Both; Child = content = new Container @@ -168,7 +169,7 @@ protected override void LoadComplete() { base.LoadComplete(); - LoadComponentAsync(drawableMedal = new DrawableMedal(medal) + LoadComponentAsync(drawableMedal = new DrawableMedal(Medal) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 072d7db6c7c1..19f61cb91029 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Online.API; @@ -81,7 +82,10 @@ private void handleMedalMessages(SocketMessage obj) }; var medalAnimation = new MedalAnimation(medal); + queuedMedals.Enqueue(medalAnimation); + Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); + if (OverlayActivationMode.Value == OverlayActivation.All) Scheduler.AddOnce(Show); } @@ -95,10 +99,12 @@ protected override void Update() if (!queuedMedals.TryDequeue(out lastAnimation)) { + Logger.Log("All queued medals have been displayed!"); Hide(); return; } + Logger.Log($"Preparing to display \"{lastAnimation.Medal.Name}\""); LoadComponentAsync(lastAnimation, medalContainer.Add); } From 3943fe96f4fe7084235c09fe1772bd6d23a9ac62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Aug 2024 20:44:35 +0900 Subject: [PATCH 399/579] Add failing test showing deserialise failing with some skins --- .../Archives/argon-invalid-drawable.osk | Bin 0 -> 2403 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/argon-invalid-drawable.osk diff --git a/osu.Game.Tests/Resources/Archives/argon-invalid-drawable.osk b/osu.Game.Tests/Resources/Archives/argon-invalid-drawable.osk new file mode 100644 index 0000000000000000000000000000000000000000..23c318149cbe3840c4219611ea22cdebf8a60a17 GIT binary patch literal 2403 zcmbtVdo)|=7C$1Xv}l8P6<1u()GM?`sX@Jpgho8$F`m(hM|9Pzbf_5G($b>TGgJp5 zwB^PSW)vOuhzjlKjCl2`*X>+d3DZ)>J?R~*R>vRiy8Byao$st~o&DQ;|MqWxc&soK z3BbVx{Z5t${An5r03ZXP0H;7Q#opbA_6Tc7V zR*z!Z3|tMmRjzue1d88NJ|ZvP(LIC1r6}_X8xvot#QEFTF-z9+?yoB7p76uAPq@i? zN3yD$Uo3y&yM@TC_a!YejzGFm`j*<9E!8RgMSENBa!k|ECCkV5ffoAW}Xf>9;jTJD=m!tQMmCL0}_OQr%?!M0Nb{Es6m3#Y_E23 zR-&9MJLuhg5JtF*zcJFCWKQbtk(tYp)FXjdQaqH2?qC5<)3 zsyE_UowF-5ZfxxM)83x3drQoEtMems<-!~Gb|(tRFDUY`SuJ>wT32Q6&~v7LfcRJo zKcpD9)^H|JOkSH<_RzvRF5@R_`H(fCUPw)v$gr%NEKN4e%h2=nO+Q3v!PwZryUi`x zM0w=DI*gE`H+ssxCnwMrmK7tPe~Hm;dBs|n-awNE`q8e=cQ={;o|q>+@=H#1D?=UH zJW#Ck*%FE}&kGCF1GwS`s7-&zGT;l4c(n;IX-=PS=lrnyR-656}Ewg<1~vbU%5Snn4yYNkEe?)~!93U9*Vn=SBI5je4;P6I6r z00PP3_Y`9is4EpvjQ;*kr>)Prdg<%=??ac?f7NAhfOPP=fS`c4PB@RZM?9|KQy{nG zgf$Tg1{h4DFd0?eSU}Z!DJLlj4W!1J3C$FJ?O0eb0}&G=^sloLGhi^2+C-C#9`&l8 zS~NOCy>>vT3*YRY(wax@QoO5Y+^i-+*BCiA{!5Rh0oq7&s&AsTj?OgHuKIi9gxOP_ znq%xix>n22^lm7f`0%~W(LU5Yr||cuS8IG4Aeu9bY%VdG!H7$ej9=_5vP-`YS<@am zZs9zwy0N{H{Sq~Dd3t_RN{|1QXQPAe+wDoCxYOp*7R_GC%kNkbTeZOy+33Eo`3sys z?p&RW)wrN+==p|a?9`vhuc>;%uiY#uWaw(VVf_-=lsIDEks|7n>U?SAkWGD>Rre)Q zL**a8nl(K2&u_DC(vkvc5P{P?@b|UA!V8N3iI(PoDX*Owi3T@^L0a$;feE2xQbfq7 z*?5JW5-JMT|IlAKV9XQn_zP%9owK+BhqXa0+p~#4KaeS8%fF7LmM(gs9uzQSK1QIJ zAC#y8VhC{dj-V4ffrAl5hm$FKWD5D?TozypaQDDgztMQ8+Y$l$@FBCt9cmycs#2C; zme0M`qtRk;HNO1<3&D$&NzQ!~kUH5Z#!tG;Q(Pz*p}#XdnNZ>3pmR_-w!)lTF&oB9 zh7#E_qwu8-ScLMzMEad4Ogr02HTU6@94l%L$CWKXl_EMa4O&!1&+y`-i!*r^-h^-H zIyutgm0l{MVScZEb#C=3vM%?|QrF`lm45OyiF2;9X&ft27F1sDyi{N%n3*8Ez4N{S zo{0aMH<=O|wg1BNu!BzSO$7XazMi|=H5W%6M-Jz@3t<>zh%tQTHr)60Y(I{Ta}qMl zGRlv!G%dYOkizn=TNYdKoA^97A2mZu=uyu|P%p|*FIrEaC6L$g!D|y#k44zIbMteg z`KsScQX_zs7-T@Mqi^#^8VB*gsWD){f}rtOAqZ0V({;OB`-1Cs$Cy;XZXY7wel{7+i>-1qLz5cK_yF{#y{@BOOa mbFaGz5_Hv$F{!iz|Mj}>9>Zf{;4c6G1J_F-0EiS3eEkQ&0G0>< literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 82b46ee75f71..73725571613e 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -12,6 +12,7 @@ using osu.Game.Audio; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Screens.Menu; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; @@ -125,6 +126,18 @@ public void TestDeserialiseModifiedArgon() } } + [Test] + public void TestDeserialiseInvalidDrawables() + { + using (var stream = TestResources.OpenResource("Archives/argon-invalid-drawable.osk")) + using (var storage = new ZipArchiveReader(stream)) + { + var skin = new TestSkin(new SkinInfo(), null, storage); + + Assert.That(skin.LayoutInfos.Any(kvp => kvp.Value.AllDrawables.Any(d => d.Type == typeof(StarFountain))), Is.False); + } + } + [Test] public void TestDeserialiseModifiedClassic() { From 885d832e9845ae1934993aa52322995fa6e4a56e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Aug 2024 20:44:45 +0900 Subject: [PATCH 400/579] Fix deserialise failing with some old skins --- osu.Game/Skinning/Skin.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 2382253036a9..e93a10d50b64 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -248,9 +248,33 @@ public void UpdateDrawableTarget(SkinnableContainer targetContainer) applyMigration(layout, target, i); layout.Version = SkinLayoutInfo.LATEST_VERSION; + + foreach (var kvp in layout.DrawableInfo.ToArray()) + { + foreach (var di in kvp.Value) + { + if (!isValidDrawable(di)) + layout.DrawableInfo[kvp.Key] = kvp.Value.Where(i => i.Type != di.Type).ToArray(); + } + } + return layout; } + private bool isValidDrawable(SerialisedDrawableInfo di) + { + if (!typeof(ISerialisableDrawable).IsAssignableFrom(di.Type)) + return false; + + foreach (var child in di.Children) + { + if (!isValidDrawable(child)) + return false; + } + + return true; + } + private void applyMigration(SkinLayoutInfo layout, GlobalSkinnableContainers target, int version) { switch (version) From f5e195a7ee2e6a4d2c72ca2ab982bf23adb160aa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Aug 2024 06:40:30 +0900 Subject: [PATCH 401/579] Remove existing test asserts --- .../Visual/UserInterface/TestSceneMainMenuButton.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index e534547c27d7..41543669ebbe 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Online.API; @@ -72,7 +71,6 @@ public void TestDailyChallengeButton() NotificationOverlay notificationOverlay = null!; DependencyProvidingContainer buttonContainer = null!; - AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234, @@ -98,7 +96,6 @@ public void TestDailyChallengeButton() }, }; }); - AddAssert("intro played flag reset", () => !Dependencies.Get().Get(Static.DailyChallengeIntroPlayed)); AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1)); AddStep("clear notifications", () => @@ -107,11 +104,8 @@ public void TestDailyChallengeButton() notification.Close(runFlingAnimation: false); }); - AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); - AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); - AddAssert("intro played flag still set", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed)); AddStep("hide button's parent", () => buttonContainer.Hide()); @@ -120,7 +114,6 @@ public void TestDailyChallengeButton() RoomID = 1234, })); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); - AddAssert("intro played flag still set", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed)); } [Test] @@ -178,13 +171,11 @@ public void TestDailyChallengeButtonOldChallenge() }; }); - AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234 })); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); - AddAssert("intro played flag reset", () => !Dependencies.Get().Get(Static.DailyChallengeIntroPlayed)); } } } From 89a4025c0109b3c96fb1265eab6485ea9a7ba9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Sat, 24 Aug 2024 10:40:51 +0200 Subject: [PATCH 402/579] Fix Daily Challenge play count using a different colour than osu-web --- .../Online/TestSceneUserProfileDailyChallenge.cs | 9 +++++++++ .../Header/Components/DailyChallengeStatsDisplay.cs | 12 ++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index f2135ec99289..d7f5f6576940 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; using osuTK; namespace osu.Game.Tests.Visual.Online @@ -60,5 +62,12 @@ private void update(Action change) change.Invoke(User.Value!.User.DailyChallengeStatistics); User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset); } + + [Test] + public void TestPlayCountRankingTier() + { + AddAssert("1 before silver", () => DailyChallengeStatsDisplay.TierForPlayCount(30) == RankingTier.Bronze); + AddAssert("first silver", () => DailyChallengeStatsDisplay.TierForPlayCount(31) == RankingTier.Silver); + } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index 82d3cfafd711..41fd2be591a9 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.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.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; @@ -11,9 +12,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; -using osu.Game.Localisation; namespace osu.Game.Overlays.Profile.Header.Components { @@ -107,15 +108,18 @@ private void updateDisplay() APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics; dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0")); - dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount)); + dailyPlayCount.Colour = colours.ForRankingTier(TierForPlayCount(stats.PlayCount)); TooltipContent = new DailyChallengeTooltipData(colourProvider, stats); Show(); - - static RankingTier tierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily(playCount / 3); } + // Rounding up is needed here to ensure the overlay shows the same colour as osu-web for the play count. + // This is because, for example, 31 / 3 > 10 in JavaScript because floats are used, while here it would + // get truncated to 10 with an integer division and show a lower tier. + public static RankingTier TierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily((int)Math.Ceiling(playCount / 3.0d)); + public ITooltip GetCustomTooltip() => new DailyChallengeStatsTooltip(); } } From b54487031ab01ffac4e6aa67c0fc33a7b3ac71c3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Aug 2024 06:40:51 +0900 Subject: [PATCH 403/579] Add `DailyChallengeButton` for intro played flag reset logic --- .../TestSceneDailyChallengeIntro.cs | 51 +++++++++++-------- .../OnlinePlay/TestRoomRequestsHandler.cs | 10 +++- osu.Game/Utils/Optional.cs | 2 +- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index 08d44d740521..f1a2d6b5f2a3 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Configuration; @@ -10,11 +9,14 @@ using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.DailyChallenge; -using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.Metadata; using osu.Game.Tests.Visual.OnlinePlay; +using osuTK.Graphics; +using osuTK.Input; using CreateRoomRequest = osu.Game.Online.Rooms.CreateRoomRequest; namespace osu.Game.Tests.Visual.DailyChallenge @@ -32,23 +34,27 @@ public partial class TestSceneDailyChallengeIntro : OnlinePlayTestScene [BackgroundDependencyLoader] private void load() { - base.Content.Add(notificationOverlay); - base.Content.Add(metadataClient); + Add(notificationOverlay); + Add(metadataClient); + + // add button to observe for daily challenge changes and perform its logic. + Add(new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)); } [Test] public void TestDailyChallenge() { - startChallenge(); + startChallenge(1234); AddStep("push screen", () => LoadScreen(new DailyChallengeIntro(room))); } [Test] public void TestPlayIntroOnceFlag() { + startChallenge(1234); AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); - startChallenge(); + startChallenge(1235); AddAssert("intro played flag reset", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed), () => Is.False); @@ -56,25 +62,28 @@ public void TestPlayIntroOnceFlag() AddUntilStep("intro played flag set", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed), () => Is.True); } - private void startChallenge() + private void startChallenge(int roomId) { - room = new Room + AddStep("add room", () => { - RoomID = { Value = 1234 }, - Name = { Value = "Daily Challenge: June 4, 2024" }, - Playlist = + API.Perform(new CreateRoomRequest(room = new Room { - new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) + RoomID = { Value = roomId }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = { - RequiredMods = [new APIMod(new OsuModTraceable())], - AllowedMods = [new APIMod(new OsuModDoubleTime())] - } - }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } - }; - - AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + new PlaylistItem(CreateAPIBeatmap(new OsuRuleset().RulesetInfo)) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + StartDate = { Value = DateTimeOffset.Now }, + EndDate = { Value = DateTimeOffset.Now.AddHours(24) }, + Category = { Value = RoomCategory.DailyChallenge } + })); + }); + AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = roomId })); } } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 91df38feb9be..4ceb946b2856 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -18,6 +18,7 @@ using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Utils; namespace osu.Game.Tests.Visual.OnlinePlay { @@ -277,11 +278,18 @@ private Room cloneRoom(Room source) var result = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source)); Debug.Assert(result != null); - // Playlist item IDs aren't serialised. + // Playlist item IDs and beatmaps aren't serialised. if (source.CurrentPlaylistItem.Value != null) + { + result.CurrentPlaylistItem.Value = result.CurrentPlaylistItem.Value.With(new Optional(source.CurrentPlaylistItem.Value.Beatmap)); result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID; + } + for (int i = 0; i < source.Playlist.Count; i++) + { + result.Playlist[i] = result.Playlist[i].With(new Optional(source.Playlist[i].Beatmap)); result.Playlist[i].ID = source.Playlist[i].ID; + } return result; } diff --git a/osu.Game/Utils/Optional.cs b/osu.Game/Utils/Optional.cs index 301767ba08db..f5749a513f50 100644 --- a/osu.Game/Utils/Optional.cs +++ b/osu.Game/Utils/Optional.cs @@ -22,7 +22,7 @@ public readonly ref struct Optional /// public readonly bool HasValue; - private Optional(T value) + public Optional(T value) { Value = value; HasValue = true; From 2bb72762ad6f18e9c7b3ad0e075731f1edcf75f9 Mon Sep 17 00:00:00 2001 From: clayton Date: Sun, 25 Aug 2024 20:42:34 -0700 Subject: [PATCH 404/579] Use more contrasting color for mod icon foreground --- osu.Game/Rulesets/UI/ModIcon.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 5d9fafd60ce9..6d91b8582371 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -204,7 +205,7 @@ private void updateExtendedInformation() private void updateColour() { - modAcronym.Colour = modIcon.Colour = OsuColour.Gray(84); + modAcronym.Colour = modIcon.Colour = Interpolation.ValueAt(0.1f, Colour4.Black, backgroundColour, 0, 1); extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour; extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f); From 70d08b9e976be5eef6de51d0088ca30130c20e71 Mon Sep 17 00:00:00 2001 From: clayton Date: Sun, 25 Aug 2024 20:42:57 -0700 Subject: [PATCH 405/579] Increase mod icon acronym font weight --- osu.Game/Rulesets/UI/ModIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 6d91b8582371..5237425075ac 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -140,7 +140,7 @@ private void load(TextureStore textures) Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = 0, - Font = OsuFont.Numeric.With(null, 22f), + Font = OsuFont.Numeric.With(size: 22f, weight: FontWeight.Black), UseFullGlyphHeight = false, Text = mod.Acronym }, From 84bceca7780c053743206a7cab4b2c5a98df4430 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2024 15:38:58 +0900 Subject: [PATCH 406/579] Assume we can always fetch the ruleset --- .../Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 47785c8868e1..0a1ac7a5a72e 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -100,7 +100,7 @@ private void load(RulesetStore rulesets, BeatmapDifficultyCache difficultyCache, StarRatingDisplay starRatingDisplay; IBeatmapInfo beatmap = item.Beatmap; - Ruleset ruleset = rulesets.GetRuleset(item.Beatmap.Ruleset.ShortName)?.CreateInstance() ?? Ruleset.Value.CreateInstance(); + Ruleset ruleset = rulesets.GetRuleset(item.Beatmap.Ruleset.ShortName)!.CreateInstance(); InternalChildren = new Drawable[] { From 4fc96ebfded8c454aaba5c77e63694af843410b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 13:13:22 +0900 Subject: [PATCH 407/579] Tidy some thing up --- .../Blueprints/Sliders/SliderCircleOverlay.cs | 80 +----------------- .../Blueprints/Sliders/SliderEndDragMarker.cs | 84 +++++++++++++++++++ .../Sliders/SliderSelectionBlueprint.cs | 23 +++-- 3 files changed, 101 insertions(+), 86 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index 6bc392627994..247ceb4078c7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -2,22 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Primitives; -using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; -using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public partial class SliderCircleOverlay : CompositeDrawable { + public SliderEndDragMarker? EndDragMarker { get; } + public RectangleF VisibleQuad { get @@ -62,8 +58,6 @@ public SliderCircleOverlay(Slider slider, SliderPosition position) } } - public SliderEndDragMarker? EndDragMarker { get; } - protected override void Update() { base.Update(); @@ -94,75 +88,5 @@ public override void Show() CirclePiece.Show(); endDragMarkerContainer?.Show(); } - - public partial class SliderEndDragMarker : SmoothPath - { - public Action? StartDrag { get; set; } - public Action? Drag { get; set; } - public Action? EndDrag { get; set; } - - [Resolved] - private OsuColour colours { get; set; } = null!; - - [BackgroundDependencyLoader] - private void load() - { - var path = PathApproximator.CircularArcToPiecewiseLinear([ - new Vector2(0, OsuHitObject.OBJECT_RADIUS), - new Vector2(OsuHitObject.OBJECT_RADIUS, 0), - new Vector2(0, -OsuHitObject.OBJECT_RADIUS) - ]); - - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - PathRadius = 5; - Vertices = path; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateState(); - } - - protected override bool OnHover(HoverEvent e) - { - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); - } - - protected override bool OnDragStart(DragStartEvent e) - { - updateState(); - StartDrag?.Invoke(e); - return true; - } - - protected override void OnDrag(DragEvent e) - { - updateState(); - base.OnDrag(e); - Drag?.Invoke(e); - } - - protected override void OnDragEnd(DragEndEvent e) - { - updateState(); - EndDrag?.Invoke(); - base.OnDragEnd(e); - } - - private void updateState() - { - Colour = IsHovered || IsDragged ? colours.Red : colours.Yellow; - } - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs new file mode 100644 index 000000000000..37383544dc59 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs @@ -0,0 +1,84 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Lines; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public partial class SliderEndDragMarker : SmoothPath + { + public Action? StartDrag { get; set; } + public Action? Drag { get; set; } + public Action? EndDrag { get; set; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + var path = PathApproximator.CircularArcToPiecewiseLinear([ + new Vector2(0, OsuHitObject.OBJECT_RADIUS), + new Vector2(OsuHitObject.OBJECT_RADIUS, 0), + new Vector2(0, -OsuHitObject.OBJECT_RADIUS) + ]); + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + PathRadius = 5; + Vertices = path; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + updateState(); + StartDrag?.Invoke(e); + return true; + } + + protected override void OnDrag(DragEvent e) + { + updateState(); + base.OnDrag(e); + Drag?.Invoke(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + updateState(); + EndDrag?.Invoke(); + base.OnDragEnd(e); + } + + private void updateState() + { + Colour = IsHovered || IsDragged ? colours.Red : colours.Yellow; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 691c053e4d13..aca704609a5c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -57,6 +58,9 @@ public partial class SliderSelectionBlueprint : OsuSelectionBlueprint [Resolved(CanBeNull = true)] private BindableBeatDivisor beatDivisor { get; set; } + [CanBeNull] + private PathControlPoint placementControlPoint; + public override Quad SelectionQuad { get @@ -84,6 +88,8 @@ public override Quad SelectionQuad // Cached slider path which ignored the expected distance value. private readonly Cached fullPathCache = new Cached(); + private Vector2 lastRightClickPosition; + public SliderSelectionBlueprint(Slider slider) : base(slider) { @@ -99,7 +105,10 @@ private void load(OsuConfigManager config) TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End), }; - TailOverlay.EndDragMarker!.StartDrag += startAdjustingLength; + // tail will always have a non-null end drag marker. + Debug.Assert(TailOverlay.EndDragMarker != null); + + TailOverlay.EndDragMarker.StartDrag += startAdjustingLength; TailOverlay.EndDragMarker.Drag += adjustLength; TailOverlay.EndDragMarker.EndDrag += endAdjustLength; @@ -154,7 +163,6 @@ protected override void Update() protected override bool OnHover(HoverEvent e) { updateVisualDefinition(); - return base.OnHover(e); } @@ -199,14 +207,12 @@ private void updateVisualDefinition() } } - private Vector2 rightClickPosition; - protected override bool OnMouseDown(MouseDownEvent e) { switch (e.Button) { case MouseButton.Right: - rightClickPosition = e.MouseDownPosition; + lastRightClickPosition = e.MouseDownPosition; return false; // Allow right click to be handled by context menu case MouseButton.Left: @@ -226,6 +232,8 @@ protected override bool OnMouseDown(MouseDownEvent e) return false; } + #region Length Adjustment (independent of path nodes) + private Vector2 lengthAdjustMouseOffset; private double oldDuration; private double oldVelocityMultiplier; @@ -351,8 +359,7 @@ private double findClosestPathDistance(MouseEvent e) return bestValue; } - [CanBeNull] - private PathControlPoint placementControlPoint; + #endregion protected override bool OnDragStart(DragStartEvent e) { @@ -586,7 +593,7 @@ private void convertToStream() new OsuMenuItem("Add control point", MenuItemType.Standard, () => { changeHandler?.BeginChange(); - addControlPoint(rightClickPosition); + addControlPoint(lastRightClickPosition); changeHandler?.EndChange(); }), new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream), From 50a8348bf9680154451fa40034d20236dc510bc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 13:18:55 +0900 Subject: [PATCH 408/579] Apply NRT to remaining classes in slider blueprint namespace --- .../Sliders/SliderPlacementBlueprint.cs | 49 ++++++++----------- .../Sliders/SliderSelectionBlueprint.cs | 40 +++++++-------- 2 files changed, 40 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 013f790f65dc..42945295b8ad 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -1,13 +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.Collections.Generic; -using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; @@ -29,30 +25,29 @@ public partial class SliderPlacementBlueprint : PlacementBlueprint { public new Slider HitObject => (Slider)base.HitObject; - private SliderBodyPiece bodyPiece; - private HitCirclePiece headCirclePiece; - private HitCirclePiece tailCirclePiece; - private PathControlPointVisualiser controlPointVisualiser; + private SliderBodyPiece bodyPiece = null!; + private HitCirclePiece headCirclePiece = null!; + private HitCirclePiece tailCirclePiece = null!; + private PathControlPointVisualiser controlPointVisualiser = null!; + + private InputManager inputManager = null!; - private InputManager inputManager; + private PathControlPoint? cursor; private SliderPlacementState state; private PathControlPoint segmentStart; - private PathControlPoint cursor; + private int currentSegmentLength; private bool usingCustomSegmentType; - [Resolved(CanBeNull = true)] - [CanBeNull] - private IPositionSnapProvider positionSnapProvider { get; set; } + [Resolved] + private IPositionSnapProvider? positionSnapProvider { get; set; } - [Resolved(CanBeNull = true)] - [CanBeNull] - private IDistanceSnapProvider distanceSnapProvider { get; set; } + [Resolved] + private IDistanceSnapProvider? distanceSnapProvider { get; set; } - [Resolved(CanBeNull = true)] - [CanBeNull] - private FreehandSliderToolboxGroup freehandToolboxGroup { get; set; } + [Resolved] + private FreehandSliderToolboxGroup? freehandToolboxGroup { get; set; } private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 }; @@ -84,7 +79,8 @@ private void load() protected override void LoadComplete() { base.LoadComplete(); - inputManager = GetContainingInputManager(); + + inputManager = GetContainingInputManager()!; if (freehandToolboxGroup != null) { @@ -108,7 +104,7 @@ protected override void LoadComplete() } [Resolved] - private EditorBeatmap editorBeatmap { get; set; } + private EditorBeatmap editorBeatmap { get; set; } = null!; public override void UpdateTimeAndPosition(SnapResult result) { @@ -151,7 +147,7 @@ protected override bool OnMouseDown(MouseDownEvent e) case SliderPlacementState.ControlPoints: if (canPlaceNewControlPoint(out var lastPoint)) placeNewControlPoint(); - else + else if (lastPoint != null) beginNewSegment(lastPoint); break; @@ -162,9 +158,6 @@ protected override bool OnMouseDown(MouseDownEvent e) private void beginNewSegment(PathControlPoint lastPoint) { - // Transform the last point into a new segment. - Debug.Assert(lastPoint != null); - segmentStart = lastPoint; segmentStart.Type = PathType.LINEAR; @@ -384,7 +377,7 @@ private Vector2 getCursorPosition() /// /// The last-placed control point. May be null, but is not null if false is returned. /// Whether a new control point can be placed at the current position. - private bool canPlaceNewControlPoint([CanBeNull] out PathControlPoint lastPoint) + private bool canPlaceNewControlPoint(out PathControlPoint? lastPoint) { // We cannot rely on the ordering of drawable pieces, so find the respective drawable piece by searching for the last non-cursor control point. var last = HitObject.Path.ControlPoints.LastOrDefault(p => p != cursor); @@ -436,7 +429,7 @@ private void updateSliderPathFromBSplineBuilder() // Replace this segment with a circular arc if it is a reasonable substitute. var circleArcSegment = tryCircleArc(segment); - if (circleArcSegment is not null) + if (circleArcSegment != null) { HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[0], PathType.PERFECT_CURVE)); HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[1])); @@ -453,7 +446,7 @@ private void updateSliderPathFromBSplineBuilder() } } - private Vector2[] tryCircleArc(List segment) + private Vector2[]? tryCircleArc(List segment) { if (segment.Count < 3 || freehandToolboxGroup?.CircleThreshold.Value == 0) return null; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index aca704609a5c..1debb09099ac 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -1,13 +1,10 @@ // 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.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -36,30 +33,28 @@ public partial class SliderSelectionBlueprint : OsuSelectionBlueprint { protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject; - protected SliderBodyPiece BodyPiece { get; private set; } - protected SliderCircleOverlay HeadOverlay { get; private set; } - protected SliderCircleOverlay TailOverlay { get; private set; } + protected SliderBodyPiece BodyPiece { get; private set; } = null!; + protected SliderCircleOverlay HeadOverlay { get; private set; } = null!; + protected SliderCircleOverlay TailOverlay { get; private set; } = null!; - [CanBeNull] - protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } + protected PathControlPointVisualiser? ControlPointVisualiser { get; private set; } - [Resolved(CanBeNull = true)] - private IDistanceSnapProvider distanceSnapProvider { get; set; } + [Resolved] + private IDistanceSnapProvider? distanceSnapProvider { get; set; } - [Resolved(CanBeNull = true)] - private IPlacementHandler placementHandler { get; set; } + [Resolved] + private IPlacementHandler? placementHandler { get; set; } - [Resolved(CanBeNull = true)] - private EditorBeatmap editorBeatmap { get; set; } + [Resolved] + private EditorBeatmap? editorBeatmap { get; set; } - [Resolved(CanBeNull = true)] - private IEditorChangeHandler changeHandler { get; set; } + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } - [Resolved(CanBeNull = true)] - private BindableBeatDivisor beatDivisor { get; set; } + [Resolved] + private BindableBeatDivisor? beatDivisor { get; set; } - [CanBeNull] - private PathControlPoint placementControlPoint; + private PathControlPoint? placementControlPoint; public override Quad SelectionQuad { @@ -145,7 +140,7 @@ public override bool HandleQuickDeletion() return false; hoveredControlPoint.IsSelected.Value = true; - ControlPointVisualiser.DeleteSelected(); + ControlPointVisualiser?.DeleteSelected(); return true; } @@ -487,6 +482,9 @@ private void removeControlPoints(List toRemove) private void splitControlPoints(List controlPointsToSplitAt) { + if (editorBeatmap == null) + return; + // Arbitrary gap in milliseconds to put between split slider pieces const double split_gap = 100; From 9840a07eaf86d9c9f652b089f0daf81cd282d2f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 14:03:14 +0900 Subject: [PATCH 409/579] Fix osu!mania hold notes playing a sound at their tail in the editor Closes #29584. --- .../Beatmaps/ManiaBeatmapConverter.cs | 13 +--------- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 24 ++++++++++++++++--- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 39ee3d209bd1..970d68759f33 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Collections.Generic; using System.Threading; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -271,7 +270,7 @@ private Pattern generate() Duration = endTimeData.Duration, Column = column, Samples = HitObject.Samples, - NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? defaultNodeSamples + NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? HoldNote.CreateDefaultNodeSamples(HitObject) }); } else if (HitObject is IHasXPosition) @@ -286,16 +285,6 @@ private Pattern generate() return pattern; } - - /// - /// osu!mania-specific beatmaps in stable only play samples at the start of the hold note. - /// - private List> defaultNodeSamples - => new List> - { - HitObject.Samples, - new List() - }; } } } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 6be0ee2d6be3..98060dd226c0 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -7,6 +7,7 @@ using System.Threading; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -91,6 +92,10 @@ protected override void CreateNestedHitObjects(CancellationToken cancellationTok { base.CreateNestedHitObjects(cancellationToken); + // Generally node samples will be populated by ManiaBeatmapConverter, but in a case like the editor they may not be. + // Ensure they are set to a sane default here. + NodeSamples ??= CreateDefaultNodeSamples(this); + AddNested(Head = new HeadNote { StartTime = StartTime, @@ -102,7 +107,7 @@ protected override void CreateNestedHitObjects(CancellationToken cancellationTok { StartTime = EndTime, Column = Column, - Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1), + Samples = GetNodeSamples(NodeSamples.Count - 1), }); AddNested(Body = new HoldNoteBody @@ -116,7 +121,20 @@ protected override void CreateNestedHitObjects(CancellationToken cancellationTok protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public IList GetNodeSamples(int nodeIndex) => - nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples; + public IList GetNodeSamples(int nodeIndex) => nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples; + + /// + /// Create the default note samples for a hold note, based off their main sample. + /// + /// + /// By default, osu!mania beatmaps in only play samples at the start of the hold note. + /// + /// The object to use as a basis for the head sample. + /// Defaults for assigning to . + public static List> CreateDefaultNodeSamples(HitObject obj) => new List> + { + obj.Samples, + new List(), + }; } } From dce32a79830721345382d799629f2f6fee710cd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 14:30:07 +0900 Subject: [PATCH 410/579] Ban `Vortice.*` imports They have colours and boxes and other classes that conflict with our naming. We never use them. --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 0c52f8d82a6b..a792b956dd41 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -845,6 +845,7 @@ See the LICENCE file in the repository root for full licence text. True True True + True True True True From c2c83fe73d7bb638c725dbb285589bd86aea0b4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 14:33:36 +0900 Subject: [PATCH 411/579] Fix `TestSceneBreakTracker` not removing old drawables Also adds a bright background for testing overlay display. --- .../Visual/Gameplay/TestSceneBreakTracker.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index c010b2c80913..ea21262fc09d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -7,9 +7,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; +using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { @@ -28,14 +30,19 @@ public partial class TestSceneBreakTracker : OsuTestScene public TestSceneBreakTracker() { - AddRange(new Drawable[] + Children = new Drawable[] { + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, breakTracker = new TestBreakTracker(), breakOverlay = new BreakOverlay(true, null) { ProcessCustomClock = false, } - }); + }; } protected override void Update() From abdbe510b884f86e5e9a5d9f746b8bddc51caac6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 14:52:08 +0900 Subject: [PATCH 412/579] Move break overlay (and cursor) further forward in depth I didn't really want to move the cursor in front of the HUD, but we face a bit of an impossible scenario otherwise (it should definitely be in front of the break overlay for visibility). So I'll deal with it for now. --- osu.Game/Screens/Play/Player.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 986c687960e9..91bd0a676bcb 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -446,14 +446,6 @@ private Drawable createOverlayComponents(IWorkingBeatmap working) Children = new[] { DimmableStoryboard.OverlayLayerContainer.CreateProxy(), - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) - { - Clock = DrawableRuleset.FrameStableClock, - ProcessCustomClock = false, - Breaks = working.Beatmap.Breaks - }, - // display the cursor above some HUD elements. - DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods, Configuration.AlwaysShowLeaderboard) { HoldToQuit = @@ -472,6 +464,14 @@ private Drawable createOverlayComponents(IWorkingBeatmap working) Anchor = Anchor.Centre, Origin = Anchor.Centre }, + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + { + Clock = DrawableRuleset.FrameStableClock, + ProcessCustomClock = false, + Breaks = working.Beatmap.Breaks + }, + // display the cursor above some HUD elements. + DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), skipIntroOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSkip = performUserRequestedSkip From 797b0207470f340456a46e55e0087fad3218d19b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 14:53:49 +0900 Subject: [PATCH 413/579] Add shadow around break overlay middle content to make sure it remains visible --- .../Screens/Play/Break/LetterboxOverlay.cs | 4 +-- osu.Game/Screens/Play/BreakOverlay.cs | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Break/LetterboxOverlay.cs b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs index c4e2dbf403ce..9308a02b0731 100644 --- a/osu.Game/Screens/Play/Break/LetterboxOverlay.cs +++ b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs @@ -11,12 +11,12 @@ namespace osu.Game.Screens.Play.Break { public partial class LetterboxOverlay : CompositeDrawable { - private const int height = 350; - private static readonly Color4 transparent_black = new Color4(0, 0, 0, 0); public LetterboxOverlay() { + const int height = 150; + RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index ece3105b4223..7480cec3a69a 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -6,11 +6,14 @@ using System; using System.Collections.Generic; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.Break; @@ -69,6 +72,30 @@ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 80, + Height = 4, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 260, + Colour = OsuColour.Gray(0.2f).Opacity(0.8f), + Roundness = 12 + }, + Children = new Drawable[] + { + new Box + { + Alpha = 0, + AlwaysPresent = true, + RelativeSizeAxes = Axes.Both, + }, + } + }, remainingTimeAdjustmentBox = new Container { Anchor = Anchor.Centre, From 98faa07590e403706a2d82ca7ec656512df6114c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 14:56:57 +0900 Subject: [PATCH 414/579] Apply NRT to `BreakOverlay` --- osu.Game/Screens/Play/BreakOverlay.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 7480cec3a69a..120d72a8e706 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.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 osu.Framework.Bindables; @@ -32,7 +30,7 @@ public partial class BreakOverlay : Container private readonly Container fadeContainer; - private IReadOnlyList breaks; + private IReadOnlyList breaks = Array.Empty(); public IReadOnlyList Breaks { @@ -138,11 +136,8 @@ protected override void LoadComplete() base.LoadComplete(); initializeBreaks(); - if (scoreProcessor != null) - { - info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy); - ((IBindable)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank); - } + info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy); + ((IBindable)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank); } protected override void Update() @@ -157,8 +152,6 @@ private void initializeBreaks() FinishTransforms(true); Scheduler.CancelDelayedTasks(); - if (breaks == null) return; // we need breaks. - foreach (var b in breaks) { if (!b.HasEffect) From 6f1664f0a60fc08995d737e40272b61742fbe580 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 16:30:49 +0900 Subject: [PATCH 415/579] Add beat-synced animation to break overlay I've been meaning to make the progress bar synchronise with the beat rather than a continuous countdown, just to give the overlay a bit more of a rhythmic feel. Not completely happy with how this feels but I think it's a start? I had to refactor how the break overlay works in the process. It no longer creates transforms for all breaks ahead-of-time, which could be argued as a better way of doing things. It's more dynamically able to handle breaks now (maybe useful for the future, who knows). --- .../Visual/Gameplay/TestSceneBreakTracker.cs | 13 +-- osu.Game/Screens/Play/BreakOverlay.cs | 100 ++++++++++-------- osu.Game/Screens/Play/BreakTracker.cs | 21 ++-- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Utils/PeriodTracker.cs | 24 ++++- 5 files changed, 100 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index ea21262fc09d..21b64958650a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osuTK.Graphics; @@ -38,9 +40,10 @@ public TestSceneBreakTracker() RelativeSizeAxes = Axes.Both, }, breakTracker = new TestBreakTracker(), - breakOverlay = new BreakOverlay(true, null) + breakOverlay = new BreakOverlay(true, new ScoreProcessor(new OsuRuleset())) { ProcessCustomClock = false, + BreakTracker = breakTracker, } }; } @@ -55,9 +58,6 @@ protected override void Update() [Test] public void TestShowBreaks() { - setClock(false); - - addShowBreakStep(2); addShowBreakStep(5); addShowBreakStep(15); } @@ -122,7 +122,7 @@ private void addShowBreakStep(double seconds) { AddStep($"show '{seconds}s' break", () => { - breakOverlay.Breaks = breakTracker.Breaks = new List + breakTracker.Breaks = new List { new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000) }; @@ -136,7 +136,7 @@ private void setClock(bool useManual) private void loadBreaksStep(string breakDescription, IReadOnlyList breaks) { - AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks); + AddStep($"load {breakDescription}", () => breakTracker.Breaks = breaks); seekAndAssertBreak("seek back to 0", 0, false); } @@ -182,6 +182,7 @@ public double ManualClockTime } public TestBreakTracker() + : base(0, new ScoreProcessor(new OsuRuleset())) { FramedManualClock = new FramedClock(manualClock = new ManualClock()); ProcessCustomClock = false; diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 120d72a8e706..7fc3dba3eb2d 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,15 +10,18 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.Break; +using osu.Game.Utils; namespace osu.Game.Screens.Play { - public partial class BreakOverlay : Container + public partial class BreakOverlay : BeatSyncedContainer { /// /// The duration of the break overlay fading. @@ -26,26 +29,14 @@ public partial class BreakOverlay : Container public const double BREAK_FADE_DURATION = BreakPeriod.MIN_BREAK_DURATION / 2; private const float remaining_time_container_max_size = 0.3f; - private const int vertical_margin = 25; + private const int vertical_margin = 15; private readonly Container fadeContainer; - private IReadOnlyList breaks = Array.Empty(); - - public IReadOnlyList Breaks - { - get => breaks; - set - { - breaks = value; - - if (IsLoaded) - initializeBreaks(); - } - } - public override bool RemoveCompletedTransforms => false; + public BreakTracker BreakTracker { get; init; } = null!; + private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeBox; private readonly RemainingTimeCounter remainingTimeCounter; @@ -53,11 +44,15 @@ public IReadOnlyList Breaks private readonly ScoreProcessor scoreProcessor; private readonly BreakInfo info; + private readonly IBindable currentPeriod = new Bindable(); + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) { this.scoreProcessor = scoreProcessor; RelativeSizeAxes = Axes.Both; + MinimumBeatLength = 200; + Child = fadeContainer = new Container { Alpha = 0, @@ -114,13 +109,13 @@ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, - Margin = new MarginPadding { Bottom = vertical_margin }, + Y = -vertical_margin, }, info = new BreakInfo { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = vertical_margin }, + Y = vertical_margin, }, breakArrows = new BreakArrows { @@ -134,51 +129,68 @@ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) protected override void LoadComplete() { base.LoadComplete(); - initializeBreaks(); info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy); ((IBindable)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank); + + currentPeriod.BindTo(BreakTracker.CurrentPeriod); + currentPeriod.BindValueChanged(updateDisplay, true); } + private float remainingTimeForCurrentPeriod => + currentPeriod.Value == null ? 0 : (float)Math.Max(0, (currentPeriod.Value.Value.End - Time.Current - BREAK_FADE_DURATION) / currentPeriod.Value.Value.Duration); + protected override void Update() { base.Update(); - remainingTimeBox.Height = Math.Min(8, remainingTimeBox.DrawWidth); + if (currentPeriod.Value != null) + { + remainingTimeBox.Height = Math.Min(8, remainingTimeBox.DrawWidth); + remainingTimeCounter.X = -(remainingTimeForCurrentPeriod - 0.5f) * 30; + info.X = (remainingTimeForCurrentPeriod - 0.5f) * 30; + } + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (currentPeriod.Value == null) + return; + + float timeBoxTargetWidth = (float)Math.Max(0, (remainingTimeForCurrentPeriod - timingPoint.BeatLength / currentPeriod.Value.Value.Duration)); + remainingTimeBox.ResizeWidthTo(timeBoxTargetWidth, timingPoint.BeatLength * 2, Easing.OutQuint); } - private void initializeBreaks() + private void updateDisplay(ValueChangedEvent period) { FinishTransforms(true); Scheduler.CancelDelayedTasks(); - foreach (var b in breaks) - { - if (!b.HasEffect) - continue; + if (period.NewValue == null) + return; - using (BeginAbsoluteSequence(b.StartTime)) - { - fadeContainer.FadeIn(BREAK_FADE_DURATION); - breakArrows.Show(BREAK_FADE_DURATION); + var b = period.NewValue.Value; - remainingTimeAdjustmentBox - .ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint) - .Delay(b.Duration - BREAK_FADE_DURATION) - .ResizeWidthTo(0); + using (BeginAbsoluteSequence(b.Start)) + { + fadeContainer.FadeIn(BREAK_FADE_DURATION); + breakArrows.Show(BREAK_FADE_DURATION); - remainingTimeBox - .ResizeWidthTo(0, b.Duration - BREAK_FADE_DURATION) - .Then() - .ResizeWidthTo(1); + remainingTimeAdjustmentBox + .ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint) + .Delay(b.Duration - BREAK_FADE_DURATION) + .ResizeWidthTo(0); - remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); + remainingTimeBox.ResizeWidthTo(remainingTimeForCurrentPeriod); - using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION)) - { - fadeContainer.FadeOut(BREAK_FADE_DURATION); - breakArrows.Hide(BREAK_FADE_DURATION); - } + remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); + + using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION)) + { + fadeContainer.FadeOut(BREAK_FADE_DURATION); + breakArrows.Hide(BREAK_FADE_DURATION); } } } diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index 20ef1dc4bfb0..3c3f31053a0d 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.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.Collections.Generic; using System.Linq; using osu.Framework.Bindables; @@ -18,7 +16,7 @@ public partial class BreakTracker : Component private readonly ScoreProcessor scoreProcessor; private readonly double gameplayStartTime; - private PeriodTracker breaks; + private PeriodTracker breaks = new PeriodTracker(Enumerable.Empty()); /// /// Whether the gameplay is currently in a break. @@ -27,6 +25,8 @@ public partial class BreakTracker : Component private readonly BindableBool isBreakTime = new BindableBool(true); + public readonly Bindable CurrentPeriod = new Bindable(); + public IReadOnlyList Breaks { set @@ -39,7 +39,7 @@ public IReadOnlyList Breaks } } - public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) + public BreakTracker(double gameplayStartTime, ScoreProcessor scoreProcessor) { this.gameplayStartTime = gameplayStartTime; this.scoreProcessor = scoreProcessor; @@ -55,9 +55,16 @@ private void updateBreakTime() { double time = Clock.CurrentTime; - isBreakTime.Value = breaks?.IsInAny(time) == true - || time < gameplayStartTime - || scoreProcessor?.HasCompleted.Value == true; + if (breaks.IsInAny(time, out var currentBreak)) + { + CurrentPeriod.Value = currentBreak; + isBreakTime.Value = true; + } + else + { + CurrentPeriod.Value = null; + isBreakTime.Value = time < gameplayStartTime || scoreProcessor.HasCompleted.Value; + } } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 91bd0a676bcb..2a66c3d5d303 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -468,7 +468,7 @@ private Drawable createOverlayComponents(IWorkingBeatmap working) { Clock = DrawableRuleset.FrameStableClock, ProcessCustomClock = false, - Breaks = working.Beatmap.Breaks + BreakTracker = breakTracker, }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), diff --git a/osu.Game/Utils/PeriodTracker.cs b/osu.Game/Utils/PeriodTracker.cs index ba77702247e2..2c62684ac419 100644 --- a/osu.Game/Utils/PeriodTracker.cs +++ b/osu.Game/Utils/PeriodTracker.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace osu.Game.Utils @@ -24,8 +25,17 @@ public PeriodTracker(IEnumerable periods) /// Whether the provided time is in any of the added periods. /// /// The time value to check. - public bool IsInAny(double time) + public bool IsInAny(double time) => IsInAny(time, out _); + + /// + /// Whether the provided time is in any of the added periods. + /// + /// The time value to check. + /// The period which matched. + public bool IsInAny(double time, [NotNullWhen(true)] out Period? period) { + period = null; + if (periods.Count == 0) return false; @@ -41,7 +51,15 @@ public bool IsInAny(double time) } var nearest = periods[nearestIndex]; - return time >= nearest.Start && time <= nearest.End; + bool isInAny = time >= nearest.Start && time <= nearest.End; + + if (isInAny) + { + period = nearest; + return true; + } + + return false; } } @@ -57,6 +75,8 @@ public readonly struct Period /// public readonly double End; + public double Duration => End - Start; + public Period(double start, double end) { if (start >= end) From 90d06d4496d727baf5da700906ad20ce7e2a66d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 16:37:27 +0900 Subject: [PATCH 416/579] Add slight parallax to centre content --- osu.Game/Screens/Play/BreakOverlay.cs | 58 +++++++++++++++------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 7fc3dba3eb2d..fd2a3cc62f86 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -89,33 +89,41 @@ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) }, } }, - remainingTimeAdjustmentBox = new Container + new ParallaxContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0, - Child = remainingTimeBox = new Circle + RelativeSizeAxes = Axes.Both, + ParallaxAmount = -0.008f, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 8, - Masking = true, - } - }, - remainingTimeCounter = new RemainingTimeCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - Y = -vertical_margin, - }, - info = new BreakInfo - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - Y = vertical_margin, + remainingTimeAdjustmentBox = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Width = 0, + Child = remainingTimeBox = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 8, + Masking = true, + } + }, + remainingTimeCounter = new RemainingTimeCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Y = -vertical_margin, + }, + info = new BreakInfo + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Y = vertical_margin, + }, + }, }, breakArrows = new BreakArrows { From e59689f31a26ff0896c5d3f8e46f2fd4598c8974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Aug 2024 09:49:49 +0200 Subject: [PATCH 417/579] Fix test and NRT failure --- osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index ea21262fc09d..ba8f9971ba13 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osuTK.Graphics; @@ -38,7 +40,7 @@ public TestSceneBreakTracker() RelativeSizeAxes = Axes.Both, }, breakTracker = new TestBreakTracker(), - breakOverlay = new BreakOverlay(true, null) + breakOverlay = new BreakOverlay(true, new ScoreProcessor(new OsuRuleset())) { ProcessCustomClock = false, } From 71044a0766fdfe39274de1ea3c6babb8dfb78a39 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 27 Aug 2024 19:02:40 +0200 Subject: [PATCH 418/579] fix difference in sample time calculation --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 6b8ea7e97e09..9bb91af80652 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1131,7 +1131,7 @@ private void seekSamplePoint(int direction) for (int i = 0; i < r.SpanCount(); i++) { - nodeSamplePointTimes[i + 2] = current.StartTime + r.Duration / r.SpanCount() * (i + 1); + nodeSamplePointTimes[i + 2] = current.StartTime + r.Duration * (i + 1) / r.SpanCount(); } double found = direction < 1 From daad4765938f5f3bd04148706af65e35e47620ce Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 27 Aug 2024 19:04:16 +0200 Subject: [PATCH 419/579] Add float comparison leniency just in case --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6cd70449432a..121cc0a301c9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -71,7 +72,7 @@ protected override void Dispose(bool isDisposing) private void onShowSampleEditPopoverRequested(double time) { - if (time == GetTime()) + if (Precision.AlmostEquals(time, GetTime())) this.ShowPopover(); } From 1117fd56a10c3b93a11f572d49b99e9533669f07 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 27 Aug 2024 19:40:18 +0200 Subject: [PATCH 420/579] change default seek hotkeys --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 542073476f5b..27d026ac9c48 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -147,10 +147,10 @@ public static IEnumerable GetGlobalActionsFor(GlobalActionCategory new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), - new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject), - new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject), - new KeyBinding(new[] { InputKey.Alt, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint), - new KeyBinding(new[] { InputKey.Alt, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), + new KeyBinding(new[] { InputKey.Control, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject), + new KeyBinding(new[] { InputKey.Control, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject), + new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint), + new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), }; private static IEnumerable editorTestPlayKeyBindings => new[] From b5b4f915a94b19c40b1d2fae32954a0dcbd0047c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 27 Aug 2024 19:40:33 +0200 Subject: [PATCH 421/579] Automatic seek to sample point on right-click --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 121cc0a301c9..488cd288e460 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -78,11 +78,18 @@ private void onShowSampleEditPopoverRequested(double time) protected override bool OnClick(ClickEvent e) { - editorClock?.SeekSmoothlyTo(GetTime()); this.ShowPopover(); return true; } + protected override void OnMouseUp(MouseUpEvent e) + { + if (e.Button != MouseButton.Right) return; + + editorClock?.SeekSmoothlyTo(GetTime()); + this.ShowPopover(); + } + private void updateText() { Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; From 47a52d10ebb488b3d9e8feaee29d113a548bd916 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 15:32:59 +0900 Subject: [PATCH 422/579] Revert "Add slight parallax to centre content" This reverts commit 90d06d4496d727baf5da700906ad20ce7e2a66d9. --- osu.Game/Screens/Play/BreakOverlay.cs | 58 ++++++++++++--------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index fd2a3cc62f86..7fc3dba3eb2d 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -89,41 +89,33 @@ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) }, } }, - new ParallaxContainer + remainingTimeAdjustmentBox = new Container { - RelativeSizeAxes = Axes.Both, - ParallaxAmount = -0.008f, - Children = new Drawable[] + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Width = 0, + Child = remainingTimeBox = new Circle { - remainingTimeAdjustmentBox = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0, - Child = remainingTimeBox = new Circle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 8, - Masking = true, - } - }, - remainingTimeCounter = new RemainingTimeCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - Y = -vertical_margin, - }, - info = new BreakInfo - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - Y = vertical_margin, - }, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 8, + Masking = true, + } + }, + remainingTimeCounter = new RemainingTimeCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Y = -vertical_margin, + }, + info = new BreakInfo + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Y = vertical_margin, }, breakArrows = new BreakArrows { From eb70a1b72d9d9476fb3a151a4af102fefd3d1593 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 15:57:42 +0900 Subject: [PATCH 423/579] Change middle text to only animate initially --- osu.Game/Screens/Play/BreakOverlay.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 7fc3dba3eb2d..7f9e879b442a 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -144,12 +144,7 @@ protected override void Update() { base.Update(); - if (currentPeriod.Value != null) - { - remainingTimeBox.Height = Math.Min(8, remainingTimeBox.DrawWidth); - remainingTimeCounter.X = -(remainingTimeForCurrentPeriod - 0.5f) * 30; - info.X = (remainingTimeForCurrentPeriod - 0.5f) * 30; - } + remainingTimeBox.Height = Math.Min(8, remainingTimeBox.DrawWidth); } protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) @@ -187,6 +182,12 @@ private void updateDisplay(ValueChangedEvent period) remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); + remainingTimeCounter.MoveToX(-50) + .MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint); + + info.MoveToX(50) + .MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint); + using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION)) { fadeContainer.FadeOut(BREAK_FADE_DURATION); From 466ed5de785e2f2f70a5077bdb8f1d527aad788d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 17:37:15 +0900 Subject: [PATCH 424/579] Add basic detached beatmap store --- osu.Game/Database/DetachedBeatmapStore.cs | 117 +++++++++++++++++++++ osu.Game/OsuGame.cs | 1 + osu.Game/Screens/Select/BeatmapCarousel.cs | 92 ++-------------- 3 files changed, 129 insertions(+), 81 deletions(-) create mode 100644 osu.Game/Database/DetachedBeatmapStore.cs diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs new file mode 100644 index 000000000000..0acc38a5a166 --- /dev/null +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -0,0 +1,117 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Beatmaps; +using Realms; + +namespace osu.Game.Database +{ + // TODO: handle realm migration + public partial class DetachedBeatmapStore : Component + { + private readonly ManualResetEventSlim loaded = new ManualResetEventSlim(); + + private List originalBeatmapSetsDetached = new List(); + + private IDisposable? subscriptionSets; + + /// + /// Track GUIDs of all sets in realm to allow handling deletions. + /// + private readonly List realmBeatmapSets = new List(); + + [Resolved] + private RealmAccess realm { get; set; } = null!; + + public IReadOnlyList GetDetachedBeatmaps() + { + if (!loaded.Wait(60000)) + Logger.Error(new TimeoutException("Beatmaps did not load in an acceptable time"), $"{nameof(DetachedBeatmapStore)} fell over"); + + return originalBeatmapSetsDetached; + } + + [BackgroundDependencyLoader] + private void load() + { + subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + } + + private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) + { + if (changes == null) + { + if (originalBeatmapSetsDetached.Count > 0 && sender.Count == 0) + { + // Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm. + // Additionally, user should not be at song select when realm is blocking all operations in the first place. + // + // Note that due to the catch-up logic below, once operations are restored we will still be in a roughly + // correct state. The only things that this return will change is the carousel will not empty *during* the blocking + // operation. + return; + } + + originalBeatmapSetsDetached = sender.Detach(); + + realmBeatmapSets.Clear(); + realmBeatmapSets.AddRange(sender.Select(r => r.ID)); + + loaded.Set(); + return; + } + + HashSet setsRequiringUpdate = new HashSet(); + HashSet setsRequiringRemoval = new HashSet(); + + foreach (int i in changes.DeletedIndices.OrderDescending()) + { + Guid id = realmBeatmapSets[i]; + + setsRequiringRemoval.Add(id); + setsRequiringUpdate.Remove(id); + + realmBeatmapSets.RemoveAt(i); + } + + foreach (int i in changes.InsertedIndices) + { + Guid id = sender[i].ID; + + setsRequiringRemoval.Remove(id); + setsRequiringUpdate.Add(id); + + realmBeatmapSets.Insert(i, id); + } + + foreach (int i in changes.NewModifiedIndices) + setsRequiringUpdate.Add(sender[i].ID); + + // deletions + foreach (Guid g in setsRequiringRemoval) + originalBeatmapSetsDetached.RemoveAll(set => set.ID == g); + + // updates + foreach (Guid g in setsRequiringUpdate) + { + originalBeatmapSetsDetached.RemoveAll(set => set.ID == g); + originalBeatmapSetsDetached.Add(fetchFromID(g)!); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + subscriptionSets?.Dispose(); + } + + private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 089db3b69895..0ef6a9467938 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1141,6 +1141,7 @@ protected override void LoadComplete() loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); + loadComponentSingleFile(new DetachedBeatmapStore(), Add, true); Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b0f198d48634..d06023258a32 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -76,8 +76,6 @@ public partial class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler originalBeatmapSetsDetached = new List(); - /// /// Raised when the is changed. /// @@ -109,6 +107,9 @@ public partial class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler beatmapSets => root.Items.OfType(); @@ -128,9 +129,7 @@ public IEnumerable BeatmapSets private void loadBeatmapSets(IEnumerable beatmapSets) { - originalBeatmapSetsDetached = beatmapSets.Detach(); - - if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) + if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; var selectedBeatmapBefore = selectedBeatmap?.BeatmapInfo; @@ -139,7 +138,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) if (beatmapsSplitOut) { - var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b => + var carouselBeatmapSets = beatmapSets.SelectMany(s => s.Beatmaps).Select(b => { return createCarouselSet(new BeatmapSetInfo(new[] { b }) { @@ -153,7 +152,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) } else { - var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType(); + var carouselBeatmapSets = beatmapSets.Select(createCarouselSet).OfType(); newRoot.AddItems(carouselBeatmapSets); } @@ -230,7 +229,7 @@ public BeatmapCarousel() } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, AudioManager audio) + private void load(OsuConfigManager config, AudioManager audio, DetachedBeatmapStore beatmapStore) { spinSample = audio.Samples.Get("SongSelect/random-spin"); randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); @@ -246,18 +245,13 @@ private void load(OsuConfigManager config, AudioManager audio) // This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons // we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update // thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time). - realm.Run(r => loadBeatmapSets(getBeatmapSets(r))); + loadBeatmapSets(detachedBeatmapStore.GetDetachedBeatmaps()); } } [Resolved] private RealmAccess realm { get; set; } = null!; - /// - /// Track GUIDs of all sets in realm to allow handling deletions. - /// - private readonly List realmBeatmapSets = new List(); - protected override void LoadComplete() { base.LoadComplete(); @@ -266,6 +260,8 @@ protected override void LoadComplete() subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); } + private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); + private readonly HashSet setsRequiringUpdate = new HashSet(); private readonly HashSet setsRequiringRemoval = new HashSet(); @@ -275,65 +271,6 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS if (loadedTestBeatmaps) return; - if (changes == null) - { - realmBeatmapSets.Clear(); - realmBeatmapSets.AddRange(sender.Select(r => r.ID)); - - if (originalBeatmapSetsDetached.Count > 0 && sender.Count == 0) - { - // Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm. - // Additionally, user should not be at song select when realm is blocking all operations in the first place. - // - // Note that due to the catch-up logic below, once operations are restored we will still be in a roughly - // correct state. The only things that this return will change is the carousel will not empty *during* the blocking - // operation. - return; - } - - // Do a full two-way check for missing (or incorrectly present) beatmaps. - // Let's assume that the worst that can happen is deletions or additions. - setsRequiringRemoval.Clear(); - setsRequiringUpdate.Clear(); - - foreach (Guid id in realmBeatmapSets) - { - if (!root.BeatmapSetsByID.ContainsKey(id)) - setsRequiringUpdate.Add(id); - } - - foreach (Guid id in root.BeatmapSetsByID.Keys) - { - if (!realmBeatmapSets.Contains(id)) - setsRequiringRemoval.Add(id); - } - } - else - { - foreach (int i in changes.DeletedIndices.OrderDescending()) - { - Guid id = realmBeatmapSets[i]; - - setsRequiringRemoval.Add(id); - setsRequiringUpdate.Remove(id); - - realmBeatmapSets.RemoveAt(i); - } - - foreach (int i in changes.InsertedIndices) - { - Guid id = sender[i].ID; - - setsRequiringRemoval.Remove(id); - setsRequiringUpdate.Add(id); - - realmBeatmapSets.Insert(i, id); - } - - foreach (int i in changes.NewModifiedIndices) - setsRequiringUpdate.Add(sender[i].ID); - } - Scheduler.AddOnce(processBeatmapChanges); } @@ -425,8 +362,6 @@ private void beatmapsChanged(IRealmCollection sender, ChangeSet? ch invalidateAfterChange(); } - private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { removeBeatmapSet(beatmapSet.ID); @@ -438,8 +373,6 @@ private void removeBeatmapSet(Guid beatmapSetID) if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets)) return; - originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSetID); - foreach (var set in existingSets) { foreach (var beatmap in set.Beatmaps) @@ -465,9 +398,6 @@ private void updateBeatmapSet(BeatmapSetInfo beatmapSet) { beatmapSet = beatmapSet.Detach(); - originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSet.ID); - originalBeatmapSetsDetached.Add(beatmapSet); - var newSets = new List(); if (beatmapsSplitOut) @@ -766,7 +696,7 @@ void perform() if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut) { beatmapsSplitOut = activeCriteria.SplitOutDifficulties; - loadBeatmapSets(originalBeatmapSetsDetached); + loadBeatmapSets(detachedBeatmapStore.GetDetachedBeatmaps()); return; } From 4d42274771b770c4cb36e05a0611a2e20a4db324 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2024 18:13:52 +0900 Subject: [PATCH 425/579] Use bindable list implementation --- osu.Game/Database/DetachedBeatmapStore.cs | 60 +++--------- osu.Game/Screens/Select/BeatmapCarousel.cs | 101 +++++++++++++-------- 2 files changed, 78 insertions(+), 83 deletions(-) diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index 0acc38a5a166..ff8178474513 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -2,10 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -13,42 +13,36 @@ namespace osu.Game.Database { - // TODO: handle realm migration public partial class DetachedBeatmapStore : Component { private readonly ManualResetEventSlim loaded = new ManualResetEventSlim(); - private List originalBeatmapSetsDetached = new List(); + private readonly BindableList detachedBeatmapSets = new BindableList(); - private IDisposable? subscriptionSets; - - /// - /// Track GUIDs of all sets in realm to allow handling deletions. - /// - private readonly List realmBeatmapSets = new List(); + private IDisposable? realmSubscription; [Resolved] private RealmAccess realm { get; set; } = null!; - public IReadOnlyList GetDetachedBeatmaps() + public IBindableList GetDetachedBeatmaps() { if (!loaded.Wait(60000)) Logger.Error(new TimeoutException("Beatmaps did not load in an acceptable time"), $"{nameof(DetachedBeatmapStore)} fell over"); - return originalBeatmapSetsDetached; + return detachedBeatmapSets.GetBoundCopy(); } [BackgroundDependencyLoader] private void load() { - subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + realmSubscription = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) { if (changes == null) { - if (originalBeatmapSetsDetached.Count > 0 && sender.Count == 0) + if (detachedBeatmapSets.Count > 0 && sender.Count == 0) { // Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm. // Additionally, user should not be at song select when realm is blocking all operations in the first place. @@ -59,57 +53,29 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS return; } - originalBeatmapSetsDetached = sender.Detach(); - - realmBeatmapSets.Clear(); - realmBeatmapSets.AddRange(sender.Select(r => r.ID)); + detachedBeatmapSets.Clear(); + detachedBeatmapSets.AddRange(sender.Detach()); loaded.Set(); return; } - HashSet setsRequiringUpdate = new HashSet(); - HashSet setsRequiringRemoval = new HashSet(); - foreach (int i in changes.DeletedIndices.OrderDescending()) - { - Guid id = realmBeatmapSets[i]; - - setsRequiringRemoval.Add(id); - setsRequiringUpdate.Remove(id); - - realmBeatmapSets.RemoveAt(i); - } + detachedBeatmapSets.RemoveAt(i); foreach (int i in changes.InsertedIndices) { - Guid id = sender[i].ID; - - setsRequiringRemoval.Remove(id); - setsRequiringUpdate.Add(id); - - realmBeatmapSets.Insert(i, id); + detachedBeatmapSets.Insert(i, sender[i].Detach()); } foreach (int i in changes.NewModifiedIndices) - setsRequiringUpdate.Add(sender[i].ID); - - // deletions - foreach (Guid g in setsRequiringRemoval) - originalBeatmapSetsDetached.RemoveAll(set => set.ID == g); - - // updates - foreach (Guid g in setsRequiringUpdate) - { - originalBeatmapSetsDetached.RemoveAll(set => set.ID == g); - originalBeatmapSetsDetached.Add(fetchFromID(g)!); - } + detachedBeatmapSets.ReplaceRange(i, 1, new[] { sender[i].Detach() }); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - subscriptionSets?.Dispose(); + realmSubscription?.Dispose(); } private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d06023258a32..118ea45e4529 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -21,6 +22,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; @@ -108,7 +110,12 @@ public partial class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler detachedBeatmapSets = null!; private readonly NoResultsPlaceholder noResultsPlaceholder; @@ -165,12 +172,6 @@ private void loadBeatmapSets(IEnumerable beatmapSets) applyActiveCriteria(false); - if (loadedTestBeatmaps) - { - invalidateAfterChange(); - BeatmapSetsLoaded = true; - } - // Restore selection if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates)) { @@ -179,6 +180,12 @@ private void loadBeatmapSets(IEnumerable beatmapSets) if (found != null) found.State.Value = CarouselItemState.Selected; } + + Schedule(() => + { + invalidateAfterChange(); + BeatmapSetsLoaded = true; + }); } private readonly List visibleItems = new List(); @@ -194,7 +201,6 @@ private void loadBeatmapSets(IEnumerable beatmapSets) private CarouselRoot root; - private IDisposable? subscriptionSets; private IDisposable? subscriptionBeatmaps; private readonly DrawablePool setPool = new DrawablePool(100); @@ -245,32 +251,62 @@ private void load(OsuConfigManager config, AudioManager audio, DetachedBeatmapSt // This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons // we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update // thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time). - loadBeatmapSets(detachedBeatmapStore.GetDetachedBeatmaps()); + detachedBeatmapSets = detachedBeatmapStore!.GetDetachedBeatmaps(); + detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); + loadBeatmapSets(detachedBeatmapSets); } } - [Resolved] - private RealmAccess realm { get; set; } = null!; - protected override void LoadComplete() { base.LoadComplete(); - subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); } - private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); - - private readonly HashSet setsRequiringUpdate = new HashSet(); - private readonly HashSet setsRequiringRemoval = new HashSet(); + private readonly HashSet setsRequiringUpdate = new HashSet(); + private readonly HashSet setsRequiringRemoval = new HashSet(); - private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) + private void beatmapSetsChanged(object? beatmaps, NotifyCollectionChangedEventArgs changed) { // If loading test beatmaps, avoid overwriting with realm subscription callbacks. if (loadedTestBeatmaps) return; + var newBeatmapSets = changed.NewItems!.Cast(); + var newBeatmapSetIDs = newBeatmapSets.Select(s => s.ID).ToHashSet(); + + var oldBeatmapSets = changed.OldItems!.Cast(); + var oldBeatmapSetIDs = oldBeatmapSets.Select(s => s.ID).ToHashSet(); + + switch (changed.Action) + { + case NotifyCollectionChangedAction.Add: + setsRequiringRemoval.RemoveWhere(s => newBeatmapSetIDs.Contains(s.ID)); + setsRequiringUpdate.AddRange(newBeatmapSets); + break; + + case NotifyCollectionChangedAction.Remove: + setsRequiringUpdate.RemoveWhere(s => oldBeatmapSetIDs.Contains(s.ID)); + setsRequiringRemoval.AddRange(oldBeatmapSets); + break; + + case NotifyCollectionChangedAction.Replace: + setsRequiringUpdate.AddRange(newBeatmapSets); + break; + + case NotifyCollectionChangedAction.Move: + setsRequiringUpdate.AddRange(newBeatmapSets); + break; + + case NotifyCollectionChangedAction.Reset: + setsRequiringRemoval.Clear(); + setsRequiringUpdate.Clear(); + + loadBeatmapSets(detachedBeatmapSets); + break; + } + Scheduler.AddOnce(processBeatmapChanges); } @@ -282,9 +318,10 @@ private void processBeatmapChanges() { try { - foreach (var set in setsRequiringRemoval) removeBeatmapSet(set); + // TODO: chekc whether we still need beatmap sets by ID + foreach (var set in setsRequiringRemoval) removeBeatmapSet(set.ID); - foreach (var set in setsRequiringUpdate) updateBeatmapSet(fetchFromID(set)!); + foreach (var set in setsRequiringUpdate) updateBeatmapSet(set); if (setsRequiringRemoval.Count > 0 && SelectedBeatmapInfo != null) { @@ -302,7 +339,7 @@ private void processBeatmapChanges() // This relies on the full update operation being in a single transaction, so please don't change that. foreach (var set in setsRequiringUpdate) { - foreach (var beatmapInfo in fetchFromID(set)!.Beatmaps) + foreach (var beatmapInfo in set.Beatmaps) { if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) continue; @@ -317,7 +354,7 @@ private void processBeatmapChanges() // If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed. // Let's attempt to follow set-level selection anyway. - SelectBeatmap(fetchFromID(setsRequiringUpdate.First())!.Beatmaps.First()); + SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First()); } } } @@ -353,7 +390,7 @@ private void beatmapsChanged(IRealmCollection sender, ChangeSet? ch if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSets) && existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID)) { - updateBeatmapSet(beatmapSet.Detach()); + updateBeatmapSet(beatmapSet); changed = true; } } @@ -383,21 +420,14 @@ private void removeBeatmapSet(Guid beatmapSetID) } } - public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) + public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { - beatmapSet = beatmapSet.Detach(); - - Schedule(() => - { - updateBeatmapSet(beatmapSet); - invalidateAfterChange(); - }); - } + updateBeatmapSet(beatmapSet); + invalidateAfterChange(); + }); private void updateBeatmapSet(BeatmapSetInfo beatmapSet) { - beatmapSet = beatmapSet.Detach(); - var newSets = new List(); if (beatmapsSplitOut) @@ -696,7 +726,7 @@ void perform() if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut) { beatmapsSplitOut = activeCriteria.SplitOutDifficulties; - loadBeatmapSets(detachedBeatmapStore.GetDetachedBeatmaps()); + loadBeatmapSets(detachedBeatmapSets); return; } @@ -1245,7 +1275,6 @@ protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - subscriptionSets?.Dispose(); subscriptionBeatmaps?.Dispose(); } } From be0e2efda2b37a31abc5318303b418709856ceb1 Mon Sep 17 00:00:00 2001 From: Fabep Date: Wed, 28 Aug 2024 09:51:17 +0200 Subject: [PATCH 426/579] Removed on click event for expanding the Mod Customisation Header. --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index abd48a0dcb27..57fe99ce4ae5 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -112,20 +112,6 @@ protected override void LoadComplete() }, true); } - protected override bool OnClick(ClickEvent e) - { - if (Enabled.Value) - { - ExpandedState.Value = ExpandedState.Value switch - { - ModCustomisationPanelState.Collapsed => ModCustomisationPanelState.Expanded, - _ => ModCustomisationPanelState.Collapsed - }; - } - - return base.OnClick(e); - } - private bool touchedThisFrame; protected override bool OnTouchDown(TouchDownEvent e) From cadbb0f27ab2937e930e86083a42a8e76101b613 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 28 Aug 2024 09:57:13 +0200 Subject: [PATCH 427/579] change sample seek keybind to ctrl shift --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 27d026ac9c48..aca0984e0fd4 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -149,8 +149,8 @@ public static IEnumerable GetGlobalActionsFor(GlobalActionCategory new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), new KeyBinding(new[] { InputKey.Control, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject), new KeyBinding(new[] { InputKey.Control, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject), - new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint), - new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), + new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint), + new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), }; private static IEnumerable editorTestPlayKeyBindings => new[] From 6adaf6a41faeeb09e9f53993c6ac2b91e9c9a0bf Mon Sep 17 00:00:00 2001 From: Fabep Date: Wed, 28 Aug 2024 10:09:47 +0200 Subject: [PATCH 428/579] Changed ModCustomisationPanelState names --- .../Visual/UserInterface/TestSceneModCustomisationPanel.cs | 6 +++--- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 2 +- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 6 +++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 0d8ea0561200..d93f62935a1a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -58,19 +58,19 @@ public void TestDisplay() { SelectedMods.Value = new[] { new OsuModDoubleTime() }; panel.Enabled.Value = true; - panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.ExpandedByMod; }); AddStep("set DA", () => { SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }; panel.Enabled.Value = true; - panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.ExpandedByMod; }); AddStep("set FL+WU+DA+AD", () => { SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }; panel.Enabled.Value = true; - panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.ExpandedByMod; }); AddStep("set empty", () => { diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 57fe99ce4ae5..5ddcf01b88be 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -130,7 +130,7 @@ protected override bool OnHover(HoverEvent e) if (Enabled.Value) { if (!touchedThisFrame && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed) - panel.ExpandedState.Value = ModCustomisationPanelState.ExpandedByHover; + panel.ExpandedState.Value = ModCustomisationPanelState.Expanded; } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 522481bc6bb5..03a1b3d0dd5a 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -227,7 +227,7 @@ protected override void Update() { base.Update(); - if (ExpandedState.Value == ModCustomisationPanelState.ExpandedByHover + if (ExpandedState.Value == ModCustomisationPanelState.Expanded && !ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position) && inputManager.DraggedDrawable == null) { @@ -239,8 +239,8 @@ protected override void Update() public enum ModCustomisationPanelState { Collapsed = 0, - ExpandedByHover = 1, - Expanded = 2, + Expanded = 1, + ExpandedByMod = 2, } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 74890df5d937..cdc0fbbd964e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -368,7 +368,7 @@ private void updateCustomisation() customisationPanel.Enabled.Value = true; if (anyModPendingConfiguration) - customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; + customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.ExpandedByMod; } else { From 081c9eb21bca77fb98094fe02463d01eb73b69d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 16:17:03 +0900 Subject: [PATCH 429/579] Fix incorrect cancellation / disposal handling of `DetachedBeatmapStore` --- osu.Game/Database/DetachedBeatmapStore.cs | 8 +++----- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index ff8178474513..55ab836dd9cc 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Game.Beatmaps; using Realms; @@ -24,11 +23,9 @@ public partial class DetachedBeatmapStore : Component [Resolved] private RealmAccess realm { get; set; } = null!; - public IBindableList GetDetachedBeatmaps() + public IBindableList GetDetachedBeatmaps(CancellationToken cancellationToken) { - if (!loaded.Wait(60000)) - Logger.Error(new TimeoutException("Beatmaps did not load in an acceptable time"), $"{nameof(DetachedBeatmapStore)} fell over"); - + loaded.Wait(cancellationToken); return detachedBeatmapSets.GetBoundCopy(); } @@ -75,6 +72,7 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + loaded.Set(); realmSubscription?.Dispose(); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 118ea45e4529..94a6087741f2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -6,6 +6,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -235,7 +236,7 @@ public BeatmapCarousel() } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, AudioManager audio, DetachedBeatmapStore beatmapStore) + private void load(OsuConfigManager config, AudioManager audio, CancellationToken cancellationToken) { spinSample = audio.Samples.Get("SongSelect/random-spin"); randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); @@ -251,7 +252,7 @@ private void load(OsuConfigManager config, AudioManager audio, DetachedBeatmapSt // This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons // we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update // thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time). - detachedBeatmapSets = detachedBeatmapStore!.GetDetachedBeatmaps(); + detachedBeatmapSets = detachedBeatmapStore!.GetDetachedBeatmaps(cancellationToken); detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); loadBeatmapSets(detachedBeatmapSets); } From 81b36d897d5869184a1bc6b397718b71f4e143d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 16:19:17 +0900 Subject: [PATCH 430/579] Fix null reference in change handling code --- osu.Game/Screens/Select/BeatmapCarousel.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 94a6087741f2..05e567b69384 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -274,30 +274,31 @@ private void beatmapSetsChanged(object? beatmaps, NotifyCollectionChangedEventAr if (loadedTestBeatmaps) return; - var newBeatmapSets = changed.NewItems!.Cast(); - var newBeatmapSetIDs = newBeatmapSets.Select(s => s.ID).ToHashSet(); - - var oldBeatmapSets = changed.OldItems!.Cast(); - var oldBeatmapSetIDs = oldBeatmapSets.Select(s => s.ID).ToHashSet(); + IEnumerable? newBeatmapSets = changed.NewItems?.Cast(); switch (changed.Action) { case NotifyCollectionChangedAction.Add: + HashSet newBeatmapSetIDs = newBeatmapSets!.Select(s => s.ID).ToHashSet(); + setsRequiringRemoval.RemoveWhere(s => newBeatmapSetIDs.Contains(s.ID)); - setsRequiringUpdate.AddRange(newBeatmapSets); + setsRequiringUpdate.AddRange(newBeatmapSets!); break; case NotifyCollectionChangedAction.Remove: + IEnumerable oldBeatmapSets = changed.OldItems!.Cast(); + HashSet oldBeatmapSetIDs = oldBeatmapSets.Select(s => s.ID).ToHashSet(); + setsRequiringUpdate.RemoveWhere(s => oldBeatmapSetIDs.Contains(s.ID)); setsRequiringRemoval.AddRange(oldBeatmapSets); break; case NotifyCollectionChangedAction.Replace: - setsRequiringUpdate.AddRange(newBeatmapSets); + setsRequiringUpdate.AddRange(newBeatmapSets!); break; case NotifyCollectionChangedAction.Move: - setsRequiringUpdate.AddRange(newBeatmapSets); + setsRequiringUpdate.AddRange(newBeatmapSets!); break; case NotifyCollectionChangedAction.Reset: From b1f653899c59cb8ef488e0e2ae09d44102f221db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 16:30:09 +0900 Subject: [PATCH 431/579] Fix enumeration over modified collection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 05e567b69384..305deb4ba9ab 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -137,6 +137,10 @@ public IEnumerable BeatmapSets private void loadBeatmapSets(IEnumerable beatmapSets) { + // Ensure no changes are made to the list while we are initialising items. + // We'll catch up on changes via subscriptions anyway. + beatmapSets = beatmapSets.ToArray(); + if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; From c2c1dccf2db2de855e9d76280eef4eed5cdcc845 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 17:46:36 +0900 Subject: [PATCH 432/579] Detach beatmap sets asynchronously --- osu.Game/Database/DetachedBeatmapStore.cs | 57 ++++++++++++++++++----- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index 55ab836dd9cc..4e5ff23f7cf3 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -30,9 +31,10 @@ public IBindableList GetDetachedBeatmaps(CancellationToken cance } [BackgroundDependencyLoader] - private void load() + private void load(CancellationToken cancellationToken) { - realmSubscription = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending && !s.Protected), beatmapSetsChanged); + loaded.Wait(cancellationToken); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) @@ -50,23 +52,56 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS return; } - detachedBeatmapSets.Clear(); - detachedBeatmapSets.AddRange(sender.Detach()); + // Detaching beatmaps takes some time, so let's make sure it doesn't run on the update thread. + var frozenSets = sender.Freeze(); + + Task.Factory.StartNew(() => + { + realm.Run(_ => + { + var detached = frozenSets.Detach(); + + detachedBeatmapSets.Clear(); + detachedBeatmapSets.AddRange(detached); + loaded.Set(); + }); + }, TaskCreationOptions.LongRunning); - loaded.Set(); return; } foreach (int i in changes.DeletedIndices.OrderDescending()) - detachedBeatmapSets.RemoveAt(i); + removeAt(i); foreach (int i in changes.InsertedIndices) - { - detachedBeatmapSets.Insert(i, sender[i].Detach()); - } + insert(sender[i].Detach(), i); foreach (int i in changes.NewModifiedIndices) - detachedBeatmapSets.ReplaceRange(i, 1, new[] { sender[i].Detach() }); + replaceRange(sender[i].Detach(), i); + } + + private void replaceRange(BeatmapSetInfo set, int i) + { + if (loaded.IsSet) + detachedBeatmapSets.ReplaceRange(i, 1, new[] { set }); + else + Schedule(() => { detachedBeatmapSets.ReplaceRange(i, 1, new[] { set }); }); + } + + private void insert(BeatmapSetInfo set, int i) + { + if (loaded.IsSet) + detachedBeatmapSets.Insert(i, set); + else + Schedule(() => { detachedBeatmapSets.Insert(i, set); }); + } + + private void removeAt(int i) + { + if (loaded.IsSet) + detachedBeatmapSets.RemoveAt(i); + else + Schedule(() => { detachedBeatmapSets.RemoveAt(i); }); } protected override void Dispose(bool isDisposing) @@ -75,7 +110,5 @@ protected override void Dispose(bool isDisposing) loaded.Set(); realmSubscription?.Dispose(); } - - private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); } } From 5ed0c6e91a9ea05c751ec9bb81b56bf17b919400 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 17:48:17 +0900 Subject: [PATCH 433/579] Remove song select preloading Really unnecessary now. --- osu.Game/Screens/Menu/MainMenu.cs | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dfe5460aee96..c1d502bd4116 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -54,8 +54,6 @@ public partial class MainMenu : OsuScreen, IHandlePresentBeatmap, IKeyBindingHan public override bool? AllowGlobalTrackControl => true; - private Screen songSelect; - private MenuSideFlashes sideFlashes; protected ButtonSystem Buttons; @@ -220,26 +218,11 @@ private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility(); reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh"); - - preloadSongSelect(); } public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial; - private void preloadSongSelect() - { - if (songSelect == null) - LoadComponentAsync(songSelect = new PlaySongSelect()); - } - - private void loadSoloSongSelect() => this.Push(consumeSongSelect()); - - private Screen consumeSongSelect() - { - var s = songSelect; - songSelect = null; - return s; - } + private void loadSoloSongSelect() => this.Push(new PlaySongSelect()); public override void OnEntering(ScreenTransitionEvent e) { @@ -373,9 +356,6 @@ public override void OnResuming(ScreenTransitionEvent e) ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next()); - // we may have consumed our preloaded instance, so let's make another. - preloadSongSelect(); - musicController.EnsurePlayingSomething(); // Cycle tip on resuming From 336abadbd1b9e36f2aa2f2ea6c05916494a19685 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 18:22:05 +0900 Subject: [PATCH 434/579] Allow running initial filter criteria asynchronously This reverts a portion of https://github.com/ppy/osu/pull/9539. The rearrangement in `SongSelect` is required to get the initial filter into `BeatmapCarousel` (and avoid the `FilterChanged` event firing, causing a delayed/scheduled filter application). --- .../SongSelect/TestSceneBeatmapCarousel.cs | 5 +++ .../TestSceneUpdateBeatmapSetButton.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 9 ++--- osu.Game/Screens/Select/SongSelect.cs | 35 ++++++++++--------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index c0102b238c8e..24be24201313 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1389,6 +1389,11 @@ private void checkInvisibleDifficultiesUnselectable() private partial class TestBeatmapCarousel : BeatmapCarousel { + public TestBeatmapCarousel() + : base(new FilterCriteria()) + { + } + public bool PendingFilterTask => PendingFilter != null; public IEnumerable Items diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 6d97be730b5a..0b0cd0317a67 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -246,7 +246,7 @@ public void TestSplitDisplay() private BeatmapCarousel createCarousel() { - return carousel = new BeatmapCarousel + return carousel = new BeatmapCarousel(new FilterCriteria()) { RelativeSizeAxes = Axes.Both, BeatmapSets = new List diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 305deb4ba9ab..5e79a8202ebb 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -170,13 +170,12 @@ private void loadBeatmapSets(IEnumerable beatmapSets) } root = newRoot; + root.Filter(activeCriteria); Scroll.Clear(false); itemsCache.Invalidate(); ScrollToSelected(); - applyActiveCriteria(false); - // Restore selection if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates)) { @@ -215,7 +214,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) private int visibleSetsCount; - public BeatmapCarousel() + public BeatmapCarousel(FilterCriteria initialCriterial) { root = new CarouselRoot(this); InternalChild = new Container @@ -237,6 +236,8 @@ public BeatmapCarousel() noResultsPlaceholder = new NoResultsPlaceholder() } }; + + activeCriteria = initialCriterial; } [BackgroundDependencyLoader] @@ -662,7 +663,7 @@ private void select(CarouselItem? item) item.State.Value = CarouselItemState.Selected; } - private FilterCriteria activeCriteria = new FilterCriteria(); + private FilterCriteria activeCriteria; protected ScheduledDelegate? PendingFilter; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2965aa383dac..3cfc7623b97d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -162,20 +162,6 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog ApplyToBackground(applyBlurToBackground); }); - LoadComponentAsync(Carousel = new BeatmapCarousel - { - AllowSelection = false, // delay any selection until our bindables are ready to make a good choice. - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - BleedTop = FilterControl.HEIGHT, - BleedBottom = Select.Footer.HEIGHT, - SelectionChanged = updateSelectedBeatmap, - BeatmapSetsChanged = carouselBeatmapsLoaded, - FilterApplied = () => Scheduler.AddOnce(updateVisibleBeatmapCount), - GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), - }, c => carouselContainer.Child = c); - // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); @@ -227,7 +213,6 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog { RelativeSizeAxes = Axes.X, Height = FilterControl.HEIGHT, - FilterChanged = ApplyFilterToCarousel, }, new GridContainer // used for max width implementation { @@ -328,6 +313,23 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), }); + // Important to load this after the filter control is loaded (so we have initial filter criteria prepared). + LoadComponentAsync(Carousel = new BeatmapCarousel(FilterControl.CreateCriteria()) + { + AllowSelection = false, // delay any selection until our bindables are ready to make a good choice. + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + BleedTop = FilterControl.HEIGHT, + BleedBottom = Select.Footer.HEIGHT, + SelectionChanged = updateSelectedBeatmap, + BeatmapSetsChanged = carouselBeatmapsLoaded, + FilterApplied = () => Scheduler.AddOnce(updateVisibleBeatmapCount), + GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), + }, c => carouselContainer.Child = c); + + FilterControl.FilterChanged = ApplyFilterToCarousel; + if (ShowSongSelectFooter) { AddRangeInternal(new Drawable[] @@ -992,7 +994,8 @@ private bool transferRulesetValue() // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). - Carousel.FlushPendingFilterOperations(); + if (IsLoaded) + Carousel.FlushPendingFilterOperations(); return true; } From dd4a1104e45ddd50015608555227fe17afdb6754 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 18:56:09 +0900 Subject: [PATCH 435/579] Always debounce external `Filter` requests (except for tests) The only exception to the rule here was "when screen isn't active apply without debounce" but I'm not sure we want this. It would cause a stutter on returning to song select and I'm not even sure this is a common scenario. I'd rather remove it and see if someone finds an actual case where this is an issue. --- .../SongSelect/TestSceneBeatmapCarousel.cs | 88 ++++++++++--------- .../SongSelect/TestScenePlaySongSelect.cs | 12 ++- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +- osu.Game/Screens/Select/SongSelect.cs | 10 +-- 4 files changed, 55 insertions(+), 59 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 24be24201313..a075559f6a22 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -52,11 +52,11 @@ public void TestExternalRulesetChange() { createCarousel(new List()); - AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria + AddStep("filter to ruleset 0", () => carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0), AllowConvertedBeatmaps = true, - }, false)); + })); AddStep("add mixed ruleset beatmapset", () => { @@ -78,11 +78,11 @@ public void TestExternalRulesetChange() && visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item)!.BeatmapInfo.Ruleset.OnlineID == 0) == 1; }); - AddStep("filter to ruleset 1", () => carousel.Filter(new FilterCriteria + AddStep("filter to ruleset 1", () => carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), AllowConvertedBeatmaps = true, - }, false)); + })); AddUntilStep("wait for filtered difficulties", () => { @@ -93,11 +93,11 @@ public void TestExternalRulesetChange() && visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item)!.BeatmapInfo.Ruleset.OnlineID == 1) == 1; }); - AddStep("filter to ruleset 2", () => carousel.Filter(new FilterCriteria + AddStep("filter to ruleset 2", () => carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(2), AllowConvertedBeatmaps = true, - }, false)); + })); AddUntilStep("wait for filtered difficulties", () => { @@ -344,7 +344,7 @@ public void TestFiltering() // basic filtering setSelected(1, 1); - AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = carousel.BeatmapSets.ElementAt(2).Metadata.Title }, false)); + AddStep("Filter", () => carousel.FilterImmediately(new FilterCriteria { SearchText = carousel.BeatmapSets.ElementAt(2).Metadata.Title })); checkVisibleItemCount(diff: false, count: 1); checkVisibleItemCount(diff: true, count: 3); waitForSelection(3, 1); @@ -360,13 +360,13 @@ public void TestFiltering() // test filtering some difficulties (and keeping current beatmap set selected). setSelected(1, 2); - AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false)); + AddStep("Filter some difficulties", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Normal" })); waitForSelection(1, 1); - AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria())); waitForSelection(1, 1); - AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false)); + AddStep("Filter all", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Dingo" })); checkVisibleItemCount(false, 0); checkVisibleItemCount(true, 0); @@ -378,7 +378,7 @@ public void TestFiltering() advanceSelection(false); AddAssert("Selection is null", () => currentSelection == null); - AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria())); AddAssert("Selection is non-null", () => currentSelection != null); @@ -399,7 +399,7 @@ public void TestFilterRange() setSelected(1, 3); - AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria + AddStep("Apply a range filter", () => carousel.FilterImmediately(new FilterCriteria { SearchText = searchText, StarDifficulty = new FilterCriteria.OptionalRange @@ -408,7 +408,7 @@ public void TestFilterRange() Max = 5.5, IsLowerInclusive = true } - }, false)); + })); // should reselect the buffered selection. waitForSelection(3, 2); @@ -445,13 +445,13 @@ public void TestRandom() AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet)); AddStep("Add set with 100 difficulties", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo(100, rulesets.AvailableRulesets.ToArray()))); - AddStep("Filter Extra", () => carousel.Filter(new FilterCriteria { SearchText = "Extra 10" }, false)); + AddStep("Filter Extra", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Extra 10" })); checkInvisibleDifficultiesUnselectable(); checkInvisibleDifficultiesUnselectable(); checkInvisibleDifficultiesUnselectable(); checkInvisibleDifficultiesUnselectable(); checkInvisibleDifficultiesUnselectable(); - AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria())); } [Test] @@ -527,7 +527,7 @@ public void TestAddRemoveDifficultySort() loadBeatmaps(setCount: local_set_count, diffCount: local_diff_count); - AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty })); checkVisibleItemCount(false, local_set_count * local_diff_count); @@ -566,7 +566,7 @@ public void TestSelectionEnteringFromEmptyRuleset() loadBeatmaps(sets, () => new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }); AddStep("Set non-empty mode filter", () => - carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }, false)); + carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) })); AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null); } @@ -601,7 +601,7 @@ public void TestSortingDateSubmitted() loadBeatmaps(sets); - AddStep("Sort by date submitted", () => carousel.Filter(new FilterCriteria { Sort = SortMode.DateSubmitted }, false)); + AddStep("Sort by date submitted", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.DateSubmitted })); checkVisibleItemCount(diff: false, count: 10); checkVisibleItemCount(diff: true, count: 5); @@ -610,11 +610,11 @@ public void TestSortingDateSubmitted() AddAssert("rest are at start", () => carousel.Items.OfType().TakeWhile(i => i.Item is CarouselBeatmapSet s && s.BeatmapSet.DateSubmitted != null).Count(), () => Is.EqualTo(6)); - AddStep("Sort by date submitted and string", () => carousel.Filter(new FilterCriteria + AddStep("Sort by date submitted and string", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.DateSubmitted, SearchText = zzz_string - }, false)); + })); checkVisibleItemCount(diff: false, count: 5); checkVisibleItemCount(diff: true, count: 5); @@ -658,10 +658,10 @@ public void TestSorting() loadBeatmaps(sets); - AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); + AddStep("Sort by author", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Author })); AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_uppercase); AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Author.Username == zzz_lowercase); - AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist })); AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_uppercase); AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase); } @@ -703,7 +703,7 @@ public void TestSortByArtistUsesTitleAsTiebreaker() loadBeatmaps(sets); - AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist })); AddAssert("Check last item", () => { var lastItem = carousel.BeatmapSets.Last(); @@ -746,10 +746,10 @@ public void TestSortingStabilityDateAdded() loadBeatmaps(sets); - AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); + AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title })); AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending); - AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist })); AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending); } @@ -786,7 +786,7 @@ public void TestSortingStabilityWithRemovedAndReaddedItem() loadBeatmaps(sets); - AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist })); AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending); AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray()); @@ -796,7 +796,7 @@ public void TestSortingStabilityWithRemovedAndReaddedItem() AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder)); - AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); + AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title })); AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder)); } @@ -833,7 +833,7 @@ public void TestSortingStabilityWithNewItems() loadBeatmaps(sets); - AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist })); AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending); AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray()); @@ -858,7 +858,7 @@ public void TestSortingStabilityWithNewItems() AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder)); - AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); + AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title })); AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder)); } @@ -885,12 +885,12 @@ public void TestSortingWithDifficultyFiltered() loadBeatmaps(sets); - AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty })); checkVisibleItemCount(false, local_set_count * local_diff_count); checkVisibleItemCount(true, 1); - AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false)); + AddStep("Filter to normal", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" })); checkVisibleItemCount(false, local_set_count); checkVisibleItemCount(true, 1); @@ -901,7 +901,7 @@ public void TestSortingWithDifficultyFiltered() .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == local_set_count; }); - AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false)); + AddStep("Filter to insane", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" })); checkVisibleItemCount(false, local_set_count); checkVisibleItemCount(true, 1); @@ -1022,7 +1022,7 @@ public void TestSelectingFilteredRuleset() carousel.UpdateBeatmapSet(testMixed); }); AddStep("filter to ruleset 0", () => - carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); + carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) })); AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 0); @@ -1068,12 +1068,12 @@ public void TestCarouselRemembersSelection() { AddStep("Toggle non-matching filter", () => { - carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false); + carousel.FilterImmediately(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }); }); AddStep("Restore no filter", () => { - carousel.Filter(new FilterCriteria(), false); + carousel.FilterImmediately(new FilterCriteria()); eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID); }); } @@ -1097,7 +1097,7 @@ public void TestCarouselRemembersSelectionDifficultySort() loadBeatmaps(manySets); - AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty })); advanceSelection(direction: 1, diff: false); @@ -1105,12 +1105,12 @@ public void TestCarouselRemembersSelectionDifficultySort() { AddStep("Toggle non-matching filter", () => { - carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false); + carousel.FilterImmediately(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }); }); AddStep("Restore no filter", () => { - carousel.Filter(new FilterCriteria(), false); + carousel.FilterImmediately(new FilterCriteria()); eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID); }); } @@ -1185,7 +1185,7 @@ public void TestCarouselSelectsNextWhenPreviousIsFiltered() AddStep($"Set ruleset to {rulesetInfo.ShortName}", () => { - carousel.Filter(new FilterCriteria { Ruleset = rulesetInfo, Sort = SortMode.Title }, false); + carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesetInfo, Sort = SortMode.Title }); }); waitForSelection(i + 1, 1); } @@ -1223,12 +1223,12 @@ public void TestCarouselSelectsBackwardsWhenDistanceIsShorter() setSelected(i, 1); AddStep("Set ruleset to taiko", () => { - carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), Sort = SortMode.Title }, false); + carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), Sort = SortMode.Title }); }); waitForSelection(i - 1, 1); AddStep("Remove ruleset filter", () => { - carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false); + carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title }); }); } @@ -1415,6 +1415,12 @@ public IEnumerable Items } } } + + public void FilterImmediately(FilterCriteria newCriteria) + { + Filter(newCriteria); + FlushPendingFilterOperations(); + } } } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 4c6a5c93d9aa..9df26e0da570 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -1397,8 +1397,6 @@ private partial class TestSongSelect : PlaySongSelect { public Action? StartRequested; - public new Bindable Ruleset => base.Ruleset; - public new FilterControl FilterControl => base.FilterControl; public WorkingBeatmap CurrentBeatmap => Beatmap.Value; @@ -1408,18 +1406,18 @@ private partial class TestSongSelect : PlaySongSelect public new void PresentScore(ScoreInfo score) => base.PresentScore(score); + public int FilterCount; + protected override bool OnStart() { StartRequested?.Invoke(); return base.OnStart(); } - public int FilterCount; - - protected override void ApplyFilterToCarousel(FilterCriteria criteria) + [BackgroundDependencyLoader] + private void load() { - FilterCount++; - base.ApplyFilterToCarousel(criteria); + FilterControl.FilterChanged += _ => FilterCount++; } } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5e79a8202ebb..ddc8f22c958a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -700,12 +700,12 @@ public void FlushPendingFilterOperations() } } - public void Filter(FilterCriteria? newCriteria, bool debounce = true) + public void Filter(FilterCriteria? newCriteria) { if (newCriteria != null) activeCriteria = newCriteria; - applyActiveCriteria(debounce); + applyActiveCriteria(true); } private bool beatmapsSplitOut; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 3cfc7623b97d..bfbc50378ab2 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -328,7 +328,7 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), }, c => carouselContainer.Child = c); - FilterControl.FilterChanged = ApplyFilterToCarousel; + FilterControl.FilterChanged = Carousel.Filter; if (ShowSongSelectFooter) { @@ -403,14 +403,6 @@ protected override bool OnScroll(ScrollEvent e) protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); - protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) - { - // if not the current screen, we want to get carousel in a good presentation state before displaying (resume or enter). - bool shouldDebounce = this.IsCurrentScreen(); - - Carousel.Filter(criteria, shouldDebounce); - } - private DependencyContainer dependencies = null!; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 9123d2cb7f18962f805b8a941f762f1c6583b73a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 19:19:04 +0900 Subject: [PATCH 436/579] Fix multiple test failures --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 6 ++++++ osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs | 6 ++++++ osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 6 ++++++ .../Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 5 +++++ .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 6 ++++++ .../Visual/Navigation/TestScenePresentBeatmap.cs | 6 ++++++ .../Visual/SongSelect/TestScenePlaySongSelect.cs | 4 ++++ osu.Game/Database/DetachedBeatmapStore.cs | 7 +++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- 9 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index aac7689b1b3e..d8be57382f32 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -18,6 +18,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -48,13 +49,18 @@ public partial class TestSceneUserDimBackgrounds : ScreenTestScene [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { + DetachedBeatmapStore detachedBeatmapStore; + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); + Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); Dependencies.Cache(Realm); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + Add(detachedBeatmapStore); + Beatmap.SetDefault(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 0f1ba9ba7516..8bcd5aab1c29 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -45,9 +46,14 @@ public abstract partial class QueueModeTestScene : ScreenTestScene [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { + DetachedBeatmapStore detachedBeatmapStore; + Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); Dependencies.Cache(Realm); + + Add(detachedBeatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index ad7e21135456..df2021dbaf1c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -19,6 +19,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -65,9 +66,14 @@ public partial class TestSceneMultiplayer : ScreenTestScene [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { + DetachedBeatmapStore detachedBeatmapStore; + Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); Dependencies.Cache(Realm); + + Add(detachedBeatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 8dc41cd70750..88cc7eb9b32a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -45,11 +45,16 @@ public partial class TestSceneMultiplayerMatchSongSelect : MultiplayerTestScene [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { + DetachedBeatmapStore detachedBeatmapStore; + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); Dependencies.Cache(Realm); importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray())); + + Add(detachedBeatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index b0b753fc2211..cc78bed5de58 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -33,13 +34,18 @@ public partial class TestScenePlaylistsSongSelect : OnlinePlayTestScene [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { + DetachedBeatmapStore detachedBeatmapStore; + Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); manager.Import(beatmapSet); + + Add(detachedBeatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index c0547921686d..fc711473f249 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -176,6 +176,12 @@ private Func importBeatmap(int i, RulesetInfo ruleset = null) private void confirmBeatmapInSongSelect(Func getImport) { + AddUntilStep("wait for carousel loaded", () => + { + var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen; + return songSelect.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; + }); + AddUntilStep("beatmap in song select", () => { var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen; diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 9df26e0da570..1f298d2d2dcb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -56,16 +56,20 @@ public partial class TestScenePlaySongSelect : ScreenTestScene [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { + DetachedBeatmapStore detachedBeatmapStore; + // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); Dependencies.Cache(music = new MusicController()); // required to get bindables attached Add(music); + Add(detachedBeatmapStore); Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); } diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index 4e5ff23f7cf3..39f0bdaafe07 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -24,17 +24,16 @@ public partial class DetachedBeatmapStore : Component [Resolved] private RealmAccess realm { get; set; } = null!; - public IBindableList GetDetachedBeatmaps(CancellationToken cancellationToken) + public IBindableList GetDetachedBeatmaps(CancellationToken? cancellationToken) { - loaded.Wait(cancellationToken); + loaded.Wait(cancellationToken ?? CancellationToken.None); return detachedBeatmapSets.GetBoundCopy(); } [BackgroundDependencyLoader] - private void load(CancellationToken cancellationToken) + private void load() { realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending && !s.Protected), beatmapSetsChanged); - loaded.Wait(cancellationToken); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ddc8f22c958a..63b2bcf7b157 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -241,7 +241,7 @@ public BeatmapCarousel(FilterCriteria initialCriterial) } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, AudioManager audio, CancellationToken cancellationToken) + private void load(OsuConfigManager config, AudioManager audio, CancellationToken? cancellationToken) { spinSample = audio.Samples.Get("SongSelect/random-spin"); randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); @@ -252,12 +252,12 @@ private void load(OsuConfigManager config, AudioManager audio, CancellationToken RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - if (!loadedTestBeatmaps) + if (!loadedTestBeatmaps && detachedBeatmapStore != null) { // This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons // we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update // thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time). - detachedBeatmapSets = detachedBeatmapStore!.GetDetachedBeatmaps(cancellationToken); + detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken); detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); loadBeatmapSets(detachedBeatmapSets); } From 853023dfbac4a328d9281eb229fc51d917c84bbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 20:14:33 +0900 Subject: [PATCH 437/579] Reduce test filter count expectation by one due to initial filter being implicit --- .../SongSelect/TestScenePlaySongSelect.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 1f298d2d2dcb..6b8fa9433666 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -246,7 +246,7 @@ public void TestSingleFilterOnEnter() createSongSelect(); - AddAssert("filter count is 1", () => songSelect?.FilterCount == 1); + AddAssert("filter count is 0", () => songSelect?.FilterCount, () => Is.EqualTo(0)); } [Test] @@ -366,7 +366,7 @@ public void TestNoFilterOnSimpleResume() AddStep("return", () => songSelect!.MakeCurrent()); AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); - AddAssert("filter count is 1", () => songSelect!.FilterCount == 1); + AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0)); } [Test] @@ -386,7 +386,7 @@ public void TestFilterOnResumeAfterChange() AddStep("return", () => songSelect!.MakeCurrent()); AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); - AddAssert("filter count is 2", () => songSelect!.FilterCount == 2); + AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1)); } [Test] @@ -1274,11 +1274,11 @@ public void TestNonFilterableModChange() // Mod that is guaranteed to never re-filter. AddStep("add non-filterable mod", () => SelectedMods.Value = new Mod[] { new OsuModCinema() }); - AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1)); + AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0)); // Removing the mod should still not re-filter. AddStep("remove non-filterable mod", () => SelectedMods.Value = Array.Empty()); - AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1)); + AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0)); } [Test] @@ -1290,35 +1290,35 @@ public void TestFilterableModChange() // Change to mania ruleset. AddStep("filter to mania ruleset", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 3)); - AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2)); + AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(1)); // Apply a mod, but this should NOT re-filter because there's no search text. AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() }); - AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2)); + AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1)); // Set search text. Should re-filter. AddStep("set search text to match mods", () => songSelect!.FilterControl.CurrentTextSearch.Value = "keys=3"); - AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3)); + AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2)); // Change filterable mod. Should re-filter. AddStep("change new filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey5() }); - AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4)); + AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3)); // Add non-filterable mod. Should NOT re-filter. AddStep("apply non-filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail(), new ManiaModKey5() }); - AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4)); + AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3)); // Remove filterable mod. Should re-filter. AddStep("remove filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail() }); - AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5)); + AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4)); // Remove non-filterable mod. Should NOT re-filter. AddStep("remove filterable mod", () => SelectedMods.Value = Array.Empty()); - AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5)); + AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4)); // Add filterable mod. Should re-filter. AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() }); - AddAssert("filter count is 6", () => songSelect!.FilterCount, () => Is.EqualTo(6)); + AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5)); } private void waitForInitialSelection() From e04b5bb3f260dd32794c00081263b6f7f61b3791 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 19:35:28 +0900 Subject: [PATCH 438/579] Tidy up test beatmap loading --- .../SongSelect/TestSceneBeatmapCarousel.cs | 16 ++++++------- osu.Game/Screens/Select/BeatmapCarousel.cs | 23 +++++++++++-------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index a075559f6a22..ec072a3dd2d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -1268,26 +1269,23 @@ private void loadBeatmaps(List beatmapSets = null, Func + createCarousel(beatmapSets, initialCriteria, c => { - carouselAdjust?.Invoke(c); - - carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria()); carousel.BeatmapSetsChanged = () => changed = true; - carousel.BeatmapSets = beatmapSets; + carouselAdjust?.Invoke(c); }); AddUntilStep("Wait for load", () => changed); } - private void createCarousel(List beatmapSets, Action carouselAdjust = null, Container target = null) + private void createCarousel(List beatmapSets, [CanBeNull] Func initialCriteria = null, Action carouselAdjust = null, Container target = null) { AddStep("Create carousel", () => { selectedSets.Clear(); eagerSelectedIDs.Clear(); - carousel = new TestBeatmapCarousel + carousel = new TestBeatmapCarousel(initialCriteria?.Invoke() ?? new FilterCriteria()) { RelativeSizeAxes = Axes.Both, }; @@ -1389,8 +1387,8 @@ private void checkInvisibleDifficultiesUnselectable() private partial class TestBeatmapCarousel : BeatmapCarousel { - public TestBeatmapCarousel() - : base(new FilterCriteria()) + public TestBeatmapCarousel(FilterCriteria criteria) + : base(criteria) { } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 63b2bcf7b157..20899d1869f4 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -130,18 +130,22 @@ public IEnumerable BeatmapSets get => beatmapSets.Select(g => g.BeatmapSet); set { + if (LoadState != LoadState.NotLoaded) + throw new InvalidOperationException("If not using a realm source, beatmap sets must be set before load."); + loadedTestBeatmaps = true; - Schedule(() => loadBeatmapSets(value)); + detachedBeatmapSets = new BindableList(value); + Schedule(loadNewRoot); } } - private void loadBeatmapSets(IEnumerable beatmapSets) + private void loadNewRoot() { // Ensure no changes are made to the list while we are initialising items. // We'll catch up on changes via subscriptions anyway. - beatmapSets = beatmapSets.ToArray(); + BeatmapSetInfo[] loadableSets = detachedBeatmapSets.ToArray(); - if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) + if (selectedBeatmapSet != null && !loadableSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; var selectedBeatmapBefore = selectedBeatmap?.BeatmapInfo; @@ -150,7 +154,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) if (beatmapsSplitOut) { - var carouselBeatmapSets = beatmapSets.SelectMany(s => s.Beatmaps).Select(b => + var carouselBeatmapSets = loadableSets.SelectMany(s => s.Beatmaps).Select(b => { return createCarouselSet(new BeatmapSetInfo(new[] { b }) { @@ -164,7 +168,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) } else { - var carouselBeatmapSets = beatmapSets.Select(createCarouselSet).OfType(); + var carouselBeatmapSets = loadableSets.Select(createCarouselSet).OfType(); newRoot.AddItems(carouselBeatmapSets); } @@ -259,7 +263,7 @@ private void load(OsuConfigManager config, AudioManager audio, CancellationToken // thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time). detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken); detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); - loadBeatmapSets(detachedBeatmapSets); + loadNewRoot(); } } @@ -309,8 +313,7 @@ private void beatmapSetsChanged(object? beatmaps, NotifyCollectionChangedEventAr case NotifyCollectionChangedAction.Reset: setsRequiringRemoval.Clear(); setsRequiringUpdate.Clear(); - - loadBeatmapSets(detachedBeatmapSets); + loadNewRoot(); break; } @@ -733,7 +736,7 @@ void perform() if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut) { beatmapsSplitOut = activeCriteria.SplitOutDifficulties; - loadBeatmapSets(detachedBeatmapSets); + loadNewRoot(); return; } From 1776d38809fbea7994614c34c489a7d740832089 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 20:06:44 +0900 Subject: [PATCH 439/579] Remove `loadedTestBeatmaps` flag --- osu.Game/Screens/Select/BeatmapCarousel.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 20899d1869f4..7f6921d768db 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -116,15 +116,12 @@ public partial class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler detachedBeatmapSets = null!; + private IBindableList? detachedBeatmapSets; private readonly NoResultsPlaceholder noResultsPlaceholder; private IEnumerable beatmapSets => root.Items.OfType(); - // todo: only used for testing, maybe remove. - private bool loadedTestBeatmaps; - public IEnumerable BeatmapSets { get => beatmapSets.Select(g => g.BeatmapSet); @@ -133,7 +130,6 @@ public IEnumerable BeatmapSets if (LoadState != LoadState.NotLoaded) throw new InvalidOperationException("If not using a realm source, beatmap sets must be set before load."); - loadedTestBeatmaps = true; detachedBeatmapSets = new BindableList(value); Schedule(loadNewRoot); } @@ -143,7 +139,7 @@ private void loadNewRoot() { // Ensure no changes are made to the list while we are initialising items. // We'll catch up on changes via subscriptions anyway. - BeatmapSetInfo[] loadableSets = detachedBeatmapSets.ToArray(); + BeatmapSetInfo[] loadableSets = detachedBeatmapSets!.ToArray(); if (selectedBeatmapSet != null && !loadableSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; @@ -256,7 +252,7 @@ private void load(OsuConfigManager config, AudioManager audio, CancellationToken RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - if (!loadedTestBeatmaps && detachedBeatmapStore != null) + if (detachedBeatmapStore != null && detachedBeatmapSets == null) { // This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons // we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update @@ -279,10 +275,6 @@ protected override void LoadComplete() private void beatmapSetsChanged(object? beatmaps, NotifyCollectionChangedEventArgs changed) { - // If loading test beatmaps, avoid overwriting with realm subscription callbacks. - if (loadedTestBeatmaps) - return; - IEnumerable? newBeatmapSets = changed.NewItems?.Cast(); switch (changed.Action) From f0b2176c300cf121a2502211e8b5835a7e78a03b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Aug 2024 22:58:57 -0700 Subject: [PATCH 440/579] Add failing pinned comment replies state test --- .../Visual/Online/TestSceneCommentsContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index acc3c9b8b466..eb805b27cb7b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -17,6 +17,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Comments; +using osu.Game.Overlays.Comments.Buttons; namespace osu.Game.Tests.Visual.Online { @@ -58,6 +59,11 @@ public void TestSingleCommentsPage(bool withPinned) AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); AddUntilStep("show more button hidden", () => commentsContainer.ChildrenOfType().Single().Alpha == 0); + + if (withPinned) + AddAssert("pinned comment replies collapsed", () => commentsContainer.ChildrenOfType().First().Expanded.Value, () => Is.False); + else + AddAssert("first comment replies expanded", () => commentsContainer.ChildrenOfType().First().Expanded.Value, () => Is.True); } [TestCase(false)] @@ -302,7 +308,7 @@ private static CommentBundle getExampleComments(bool withPinned = false) bundle.Comments.Add(new Comment { Id = 20, - Message = "Reply to pinned comment", + Message = "Reply to pinned comment initially hidden", LegacyName = "AbandonedUser", CreatedAt = DateTimeOffset.Now, VotesCount = 0, From ef443b0b5d191110947c23e151c0878607fb2b0b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Aug 2024 23:00:16 -0700 Subject: [PATCH 441/579] Hide pinned comment replies initially to match web --- osu.Game/Overlays/Comments/DrawableComment.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index afd4b96c6882..296f90872e55 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -47,7 +47,7 @@ public partial class DrawableComment : CompositeDrawable public readonly BindableList Replies = new BindableList(); - private readonly BindableBool childrenExpanded = new BindableBool(true); + private readonly BindableBool childrenExpanded; private int currentPage; @@ -92,6 +92,8 @@ public DrawableComment(Comment comment, IReadOnlyList meta) { Comment = comment; Meta = meta; + + childrenExpanded = new BindableBool(!comment.Pinned); } [BackgroundDependencyLoader] From def1abaeca06161faee6422d8efbe1c68b03c4f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2024 23:29:32 +0900 Subject: [PATCH 442/579] Fix some tests not always waiting long enough for beatmap loading These used to work because there was a huge blocking load operation, which is now more asynchronous. Note that the change made in `SongSelect` is not required, but defensive (feels it should have been doing this the whole time). --- .../Visual/Editing/TestSceneOpenEditorTimestamp.cs | 4 ++-- .../Navigation/TestSceneBeatmapEditorNavigation.cs | 12 +++++++----- .../Visual/Navigation/TestScenePresentBeatmap.cs | 4 ++-- .../Visual/Navigation/TestSceneScreenNavigation.cs | 6 ++++-- osu.Game/Screens/Select/SongSelect.cs | 3 ++- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 1f46a088314d..971eb223eba8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -36,7 +36,7 @@ public void TestErrorNotifications() () => Is.EqualTo(1)); AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); - AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); addStepClickLink("00:00:000 (1)", waitForSeek: false); AddUntilStep("received 'must be in edit'", @@ -138,7 +138,7 @@ private void setUpEditor(RulesetInfo ruleset) AddUntilStep("Wait for song select", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded + && songSelect.BeatmapSetsLoaded ); AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset); AddStep("Open editor for ruleset", () => diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 5640682d0637..d76e0290efb5 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -165,16 +165,19 @@ private void makeMetadataChange(bool commit = true) } [Test] + [Solo] public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() { prepareBeatmap(); - AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); + AddStep("switch ruleset at song select", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); - AddStep("test gameplay", () => getEditor().TestGameplay()); + AddAssert("editor ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo)); + AddStep("test gameplay", () => getEditor().TestGameplay()); AddUntilStep("wait for player", () => { // notifications may fire at almost any inopportune time and cause annoying test failures. @@ -183,8 +186,7 @@ public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() Game.CloseAllOverlays(); return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded; }); - - AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); + AddAssert("gameplay ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo)); AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); @@ -352,7 +354,7 @@ private void prepareBeatmap() AddUntilStep("wait for song select", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); + && songSelect.BeatmapSetsLoaded); } private void openEditor() diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index fc711473f249..f036b4b3ef9f 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -193,7 +193,7 @@ private void presentAndConfirm(Func getImport) { AddStep("present beatmap", () => Game.PresentBeatmap(getImport())); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded); AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID, () => Is.EqualTo(getImport().OnlineID)); AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset)); } @@ -203,7 +203,7 @@ private void presentSecondDifficultyAndConfirm(Func getImport, i Predicate pred = b => b.OnlineID == importedID * 1024 + 2; AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred)); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded); AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(importedID * 1024 + 2)); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.OnlineID, () => Is.EqualTo(expectedRulesetOnlineID ?? getImport().Beatmaps.First().Ruleset.OnlineID)); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index db9ecd90b9fe..f02c2fd4f093 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -1035,9 +1035,11 @@ public void TestTouchScreenDetectionAtSongSelect() [Test] public void TestTouchScreenDetectionInGame() { + BeatmapSetInfo beatmapSet = null; + PushAndConfirm(() => new TestPlaySongSelect()); - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely()); + AddUntilStep("wait for selected", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)); AddStep("select", () => InputManager.Key(Key.Enter)); Player player = null; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bfbc50378ab2..6da72ee66077 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -428,7 +428,8 @@ public void Edit(BeatmapInfo? beatmapInfo = null) // Forced refetch is important here to guarantee correct invalidation across all difficulties. Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce, true); - this.Push(new EditorLoader()); + + FinaliseSelection(customStartAction: () => this.Push(new EditorLoader())); } /// From d1d2591b6737c9fa6ee5806d6c2c1038db0aba57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Aug 2024 18:29:58 +0900 Subject: [PATCH 443/579] Fix realm changes being applied before detach finishes --- osu.Game/Database/DetachedBeatmapStore.cs | 85 +++++++++++++++++------ 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index 39f0bdaafe07..17d2dd15b603 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -21,6 +22,8 @@ public partial class DetachedBeatmapStore : Component private IDisposable? realmSubscription; + private readonly Queue pendingOperations = new Queue(); + [Resolved] private RealmAccess realm { get; set; } = null!; @@ -70,37 +73,61 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS } foreach (int i in changes.DeletedIndices.OrderDescending()) - removeAt(i); + { + pendingOperations.Enqueue(new OperationArgs + { + Type = OperationType.Remove, + Index = i, + }); + } foreach (int i in changes.InsertedIndices) - insert(sender[i].Detach(), i); + { + pendingOperations.Enqueue(new OperationArgs + { + Type = OperationType.Insert, + BeatmapSet = sender[i].Detach(), + Index = i, + }); + } foreach (int i in changes.NewModifiedIndices) - replaceRange(sender[i].Detach(), i); + { + pendingOperations.Enqueue(new OperationArgs + { + Type = OperationType.Update, + BeatmapSet = sender[i].Detach(), + Index = i, + }); + } } - private void replaceRange(BeatmapSetInfo set, int i) + protected override void Update() { - if (loaded.IsSet) - detachedBeatmapSets.ReplaceRange(i, 1, new[] { set }); - else - Schedule(() => { detachedBeatmapSets.ReplaceRange(i, 1, new[] { set }); }); - } + base.Update(); - private void insert(BeatmapSetInfo set, int i) - { - if (loaded.IsSet) - detachedBeatmapSets.Insert(i, set); - else - Schedule(() => { detachedBeatmapSets.Insert(i, set); }); - } + // We can't start processing operations until we have finished detaching the initial list. + if (!loaded.IsSet) + return; - private void removeAt(int i) - { - if (loaded.IsSet) - detachedBeatmapSets.RemoveAt(i); - else - Schedule(() => { detachedBeatmapSets.RemoveAt(i); }); + // If this ever leads to performance issues, we could dequeue a limited number of operations per update frame. + while (pendingOperations.TryDequeue(out var op)) + { + switch (op.Type) + { + case OperationType.Insert: + detachedBeatmapSets.Insert(op.Index, op.BeatmapSet!); + break; + + case OperationType.Update: + detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! }); + break; + + case OperationType.Remove: + detachedBeatmapSets.RemoveAt(op.Index); + break; + } + } } protected override void Dispose(bool isDisposing) @@ -109,5 +136,19 @@ protected override void Dispose(bool isDisposing) loaded.Set(); realmSubscription?.Dispose(); } + + private record OperationArgs + { + public OperationType Type; + public BeatmapSetInfo? BeatmapSet; + public int Index; + } + + private enum OperationType + { + Insert, + Update, + Remove + } } } From 97adac2e0ae235eff15213e6f89dc6dcddd245a6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 29 Aug 2024 15:31:02 +0900 Subject: [PATCH 444/579] Add test + adjust existing ones with new semantics --- .../Filtering/FilterQueryParserTest.cs | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 7897b3d8c0f9..e6006b7fd239 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -537,7 +537,7 @@ public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) [TestCaseSource(nameof(correct_date_query_examples))] public void TestValidDateQueries(string dateQuery) { - string query = $"played<{dateQuery} time"; + string query = $"lastplayed<{dateQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); @@ -571,7 +571,7 @@ public void TestInvalidDateQueries(string dateQuery) [Test] public void TestGreaterDateQuery() { - const string query = "played>50"; + const string query = "lastplayed>50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null); @@ -584,7 +584,7 @@ public void TestGreaterDateQuery() [Test] public void TestLowerDateQuery() { - const string query = "played<50"; + const string query = "lastplayed<50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.That(filterCriteria.LastPlayed.Max, Is.Null); @@ -597,7 +597,7 @@ public void TestLowerDateQuery() [Test] public void TestBothSidesDateQuery() { - const string query = "played>3M played<1y6M"; + const string query = "lastplayed>3M lastplayed<1y6M"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null); @@ -611,7 +611,7 @@ public void TestBothSidesDateQuery() [Test] public void TestEqualDateQuery() { - const string query = "played=50"; + const string query = "lastplayed=50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter); @@ -620,11 +620,34 @@ public void TestEqualDateQuery() [Test] public void TestOutOfRangeDateQuery() { - const string query = "played<10000y"; + const string query = "lastplayed<10000y"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min); } + + private static readonly object[] played_query_tests = + { + new object[] { "0", DateTimeOffset.MinValue, true }, + new object[] { "0", DateTimeOffset.Now, false }, + new object[] { "false", DateTimeOffset.MinValue, true }, + new object[] { "false", DateTimeOffset.Now, false }, + + new object[] { "1", DateTimeOffset.MinValue, false }, + new object[] { "1", DateTimeOffset.Now, true }, + new object[] { "true", DateTimeOffset.MinValue, false }, + new object[] { "true", DateTimeOffset.Now, true }, + }; + + [Test] + [TestCaseSource(nameof(played_query_tests))] + public void TestPlayedQuery(string query, DateTimeOffset reference, bool matched) + { + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, $"played={query}"); + Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); + Assert.AreEqual(matched, filterCriteria.LastPlayed.IsInRange(reference)); + } } } From fde790c014179ab88a381918dc3b8e5354f8173d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 29 Aug 2024 15:32:35 +0900 Subject: [PATCH 445/579] Rework `played` filter to a boolean value --- osu.Game/Screens/Select/FilterQueryParser.cs | 40 +++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 40fd289be620..3e0dba59f09b 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -62,10 +62,31 @@ private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, case "length": return tryUpdateLengthRange(criteria, op, value); - case "played": case "lastplayed": return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value); + case "played": + if (!tryParseBool(value, out bool played)) + return false; + + // Unplayed beatmaps are filtered on DateTimeOffset.MinValue. + + if (played) + { + criteria.LastPlayed.Min = DateTimeOffset.MinValue; + criteria.LastPlayed.Max = DateTimeOffset.MaxValue; + criteria.LastPlayed.IsLowerInclusive = false; + } + else + { + criteria.LastPlayed.Min = DateTimeOffset.MinValue; + criteria.LastPlayed.Max = DateTimeOffset.MinValue; + criteria.LastPlayed.IsLowerInclusive = true; + criteria.LastPlayed.IsUpperInclusive = true; + } + + return true; + case "divisor": return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); @@ -133,6 +154,23 @@ private static bool tryParseDoubleWithPoint(string value, out double result) => private static bool tryParseInt(string value, out int result) => int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); + private static bool tryParseBool(string value, out bool result) + { + switch (value) + { + case "1": + result = true; + return true; + + case "0": + result = false; + return true; + + default: + return bool.TryParse(value, out result); + } + } + private static bool tryParseEnum(string value, out TEnum result) where TEnum : struct { // First try an exact match. From 7435e8aa00a35e91b5334126384eae7182ad1ed2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 30 Aug 2024 00:48:53 +0900 Subject: [PATCH 446/579] Fix catch auto generator not considering circle size --- osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 7c84cb24f39d..7c62f9692f99 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -16,9 +16,12 @@ internal class CatchAutoGenerator : AutoGenerator { public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap; + private readonly float halfCatcherWidth; + public CatchAutoGenerator(IBeatmap beatmap) : base(beatmap) { + halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f; } protected override void GenerateFrames() @@ -47,10 +50,7 @@ void moveToNext(PalpableCatchHitObject h) bool dashRequired = speedRequired > Catcher.BASE_WALK_SPEED; bool impossibleJump = speedRequired > Catcher.BASE_DASH_SPEED; - // todo: get correct catcher size, based on difficulty CS. - const float catcher_width_half = Catcher.BASE_SIZE * 0.3f * 0.5f; - - if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX) + if (lastPosition - halfCatcherWidth < h.EffectiveX && lastPosition + halfCatcherWidth > h.EffectiveX) { // we are already in the correct range. lastTime = h.StartTime; From 8fe7ab131ca810d3397603aa6dee0e67237b5911 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 29 Aug 2024 19:34:14 +0200 Subject: [PATCH 447/579] dont seek on right-click, only on keyboard request --- .../Components/Timeline/SamplePointPiece.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 488cd288e460..a8cf8723f2d0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -72,22 +72,16 @@ protected override void Dispose(bool isDisposing) private void onShowSampleEditPopoverRequested(double time) { - if (Precision.AlmostEquals(time, GetTime())) - this.ShowPopover(); - } + if (!Precision.AlmostEquals(time, GetTime())) return; - protected override bool OnClick(ClickEvent e) - { + editorClock?.SeekSmoothlyTo(GetTime()); this.ShowPopover(); - return true; } - protected override void OnMouseUp(MouseUpEvent e) + protected override bool OnClick(ClickEvent e) { - if (e.Button != MouseButton.Right) return; - - editorClock?.SeekSmoothlyTo(GetTime()); this.ShowPopover(); + return true; } private void updateText() From 3a1afda2b3c41a9756675b85d878b9d21901fdeb Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 29 Aug 2024 22:22:15 +0200 Subject: [PATCH 448/579] fix test --- .../Editing/TestSceneHitObjectSampleAdjustments.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 3e663aea0fcc..3c5277a4d9ad 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -332,15 +332,6 @@ public void TestSamplePointSeek() }); }); - clickNodeSamplePiece(0, 0); - editorTimeIs(0); - clickNodeSamplePiece(0, 1); - editorTimeIs(813); - clickNodeSamplePiece(0, 2); - editorTimeIs(1627); - clickSamplePiece(0); - editorTimeIs(406); - seekSamplePiece(-1); editorTimeIs(0); samplePopoverIsOpen(); @@ -692,11 +683,11 @@ private void clickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"c private void seekSamplePiece(int direction) => AddStep($"seek sample piece {direction}", () => { + InputManager.PressKey(Key.ControlLeft); InputManager.PressKey(Key.ShiftLeft); - InputManager.PressKey(Key.AltLeft); InputManager.Key(direction < 1 ? Key.Left : Key.Right); - InputManager.ReleaseKey(Key.AltLeft); InputManager.ReleaseKey(Key.ShiftLeft); + InputManager.ReleaseKey(Key.ControlLeft); }); private void samplePopoverIsOpen() => AddUntilStep("sample popover is open", () => From 3bc42db3a612a0fdd97aeeaf0a94d4588b088326 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 16:13:30 +0900 Subject: [PATCH 449/579] Fix event leak in `Multiplayer` implementation Very likely closes #29088. It's the only thing I could find odd in the memory dump. --- osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index 7d27725775c1..bf316bb3da5b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -108,7 +108,10 @@ protected override void Dispose(bool isDisposing) base.Dispose(isDisposing); if (client.IsNotNull()) + { client.RoomUpdated -= onRoomUpdated; + client.GameplayAborted -= onGameplayAborted; + } } } } From 7f41d5f4e7e7fa0b27192a2eb5ba85045508e8a1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 30 Aug 2024 16:32:15 +0900 Subject: [PATCH 450/579] Remove mouse input from mania touch controls --- .../UI/ManiaTouchInputArea.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs index 453b75ac84fd..8c4a71cf2407 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs @@ -99,12 +99,6 @@ protected override bool OnKeyDown(KeyDownEvent e) return false; } - protected override bool OnMouseDown(MouseDownEvent e) - { - Show(); - return true; - } - protected override bool OnTouchDown(TouchDownEvent e) { Show(); @@ -172,17 +166,6 @@ protected override void OnTouchUp(TouchUpEvent e) updateButton(false); } - protected override bool OnMouseDown(MouseDownEvent e) - { - updateButton(true); - return false; // handled by parent container to show overlay. - } - - protected override void OnMouseUp(MouseUpEvent e) - { - updateButton(false); - } - private void updateButton(bool press) { if (press == isPressed) From 5836f497ac13d168ab077946a67f8d349079794f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:03:30 +0900 Subject: [PATCH 451/579] Provide API context earlier to api requests in order to fix missing schedules Closes https://github.com/ppy/osu/issues/29546. --- osu.Game/Online/API/APIAccess.cs | 8 +++++-- osu.Game/Online/API/APIRequest.cs | 37 +++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 716d1e44669d..a9ad56116301 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -385,7 +385,8 @@ public void Perform(APIRequest request) { try { - request.Perform(this); + request.AttachAPI(this); + request.Perform(); } catch (Exception e) { @@ -483,7 +484,8 @@ private bool handleRequest(APIRequest req) { try { - req.Perform(this); + req.AttachAPI(this); + req.Perform(); if (req.CompletionState != APIRequestCompletionState.Completed) return false; @@ -568,6 +570,8 @@ public void Queue(APIRequest request) { lock (queue) { + request.AttachAPI(this); + if (state.Value == APIState.Offline) { request.Fail(new WebException(@"User not logged in")); diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 6b6b22204397..d062b8f3de92 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Diagnostics; using System.Globalization; using JetBrains.Annotations; using Newtonsoft.Json; @@ -74,6 +75,7 @@ public abstract class APIRequest protected virtual string Uri => $@"{API.APIEndpointUrl}/api/v2/{Target}"; protected APIAccess API; + protected WebRequest WebRequest; /// @@ -101,16 +103,29 @@ public abstract class APIRequest /// public APIRequestCompletionState CompletionState { get; private set; } - public void Perform(IAPIProvider api) + /// + /// Should be called before to give API context. + /// + /// + /// This allows scheduling of operations back to the correct thread (which may be required before is called). + /// + public void AttachAPI(APIAccess apiAccess) + { + if (API != null && API != apiAccess) + throw new InvalidOperationException("Attached API cannot be changed after initial set."); + + API = apiAccess; + } + + public void Perform() { - if (!(api is APIAccess apiAccess)) + if (API == null) { Fail(new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests.")); return; } - API = apiAccess; - User = apiAccess.LocalUser.Value; + User = API.LocalUser.Value; if (isFailing) return; @@ -153,6 +168,8 @@ protected virtual void PostProcess() internal void TriggerSuccess() { + Debug.Assert(API != null); + lock (completionStateLock) { if (CompletionState != APIRequestCompletionState.Waiting) @@ -161,14 +178,13 @@ internal void TriggerSuccess() CompletionState = APIRequestCompletionState.Completed; } - if (API == null) - Success?.Invoke(); - else - API.Schedule(() => Success?.Invoke()); + API.Schedule(() => Success?.Invoke()); } internal void TriggerFailure(Exception e) { + Debug.Assert(API != null); + lock (completionStateLock) { if (CompletionState != APIRequestCompletionState.Waiting) @@ -177,10 +193,7 @@ internal void TriggerFailure(Exception e) CompletionState = APIRequestCompletionState.Failed; } - if (API == null) - Failure?.Invoke(e); - else - API.Schedule(() => Failure?.Invoke(e)); + API.Schedule(() => Failure?.Invoke(e)); } public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); From 07611bd8f5f30617a78649cc9eb513c89844a552 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:10:33 +0900 Subject: [PATCH 452/579] Use `IAPIProvider` interface and correctly support scheduling from `DummyAPIAccess` --- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Online/API/APIRequest.cs | 4 ++-- osu.Game/Online/API/DummyAPIAccess.cs | 13 ++++++++++++- osu.Game/Online/API/IAPIProvider.cs | 5 +++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a9ad56116301..a9ccbf9b1889 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -159,7 +159,7 @@ private WebSocketNotificationsClientConnector setUpNotificationsClient() private void onTokenChanged(ValueChangedEvent e) => config.SetValue(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty); - internal new void Schedule(Action action) => base.Schedule(action); + void IAPIProvider.Schedule(Action action) => base.Schedule(action); public string AccessToken => authentication.RequestAccessToken(); diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index d062b8f3de92..37ad5fff0e04 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -74,7 +74,7 @@ public abstract class APIRequest protected virtual string Uri => $@"{API.APIEndpointUrl}/api/v2/{Target}"; - protected APIAccess API; + protected IAPIProvider API; protected WebRequest WebRequest; @@ -109,7 +109,7 @@ public abstract class APIRequest /// /// This allows scheduling of operations back to the correct thread (which may be required before is called). /// - public void AttachAPI(APIAccess apiAccess) + public void AttachAPI(IAPIProvider apiAccess) { if (API != null && API != apiAccess) throw new InvalidOperationException("Attached API cannot be changed after initial set."); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 0af76537cd76..7ac5c45fada0 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -82,6 +82,8 @@ public DummyAPIAccess() public virtual void Queue(APIRequest request) { + request.AttachAPI(this); + Schedule(() => { if (HandleRequest?.Invoke(request) != true) @@ -98,10 +100,17 @@ public virtual void Queue(APIRequest request) }); } - public void Perform(APIRequest request) => HandleRequest?.Invoke(request); + void IAPIProvider.Schedule(Action action) => base.Schedule(action); + + public void Perform(APIRequest request) + { + request.AttachAPI(this); + HandleRequest?.Invoke(request); + } public Task PerformAsync(APIRequest request) { + request.AttachAPI(this); HandleRequest?.Invoke(request); return Task.CompletedTask; } @@ -155,6 +164,8 @@ public void AuthenticateSecondFactor(string code) state.Value = APIState.Connecting; LastLoginError = null; + request.AttachAPI(this); + // if no handler installed / handler can't handle verification, just assume that the server would verify for simplicity. if (HandleRequest?.Invoke(request) != true) onSuccessfulLogin(); diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index d8194dc32b19..eccfb3654659 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -134,6 +134,11 @@ public interface IAPIProvider /// void UpdateStatistics(UserStatistics newStatistics); + /// + /// Schedule a callback to run on the update thread. + /// + internal void Schedule(Action action); + /// /// Constructs a new . May be null if not supported. /// From dd7133657dbe57c3aa99ef8266b52ca6bebf62a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:14:10 +0900 Subject: [PATCH 453/579] Fix weird test critical failure if exception happens too early in execution Noticed in passing. ``` Exit code is 134 (Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. at osu.Game.OsuGameBase.onExceptionThrown(Exception ex) in /Users/dean/Projects/osu/osu.Game/OsuGameBase.cs:line 695 at osu.Framework.Platform.GameHost.abortExecutionFromException(Object sender, Exception exception, Boolean isTerminating) at osu.Framework.Platform.GameHost.unobservedExceptionHandler(Object sender, UnobservedTaskExceptionEventArgs args) at System.Threading.Tasks.TaskExceptionHolder.Finalize()) ``` --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1988a06503ee..ce0c288934ee 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -692,7 +692,7 @@ private bool onExceptionThrown(Exception ex) if (Interlocked.Decrement(ref allowableExceptions) < 0) { Logger.Log("Too many unhandled exceptions, crashing out."); - RulesetStore.TryDisableCustomRulesetsCausing(ex); + RulesetStore?.TryDisableCustomRulesetsCausing(ex); return false; } From 2d745fb67e9210ae4eb7d0b5a702729ca4ac8ce3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:21:30 +0900 Subject: [PATCH 454/579] Apply NRT to `APIRequest` --- osu.Game/Online/API/APIRequest.cs | 26 ++++++++----------- .../Online/API/Requests/JoinChannelRequest.cs | 2 +- .../API/Requests/LeaveChannelRequest.cs | 2 +- osu.Game/Online/Chat/WebSocketChatClient.cs | 2 +- osu.Game/Online/Rooms/JoinRoomRequest.cs | 2 +- osu.Game/Online/Rooms/PartRoomRequest.cs | 2 +- osu.Game/Tests/PollingChatClient.cs | 2 +- 7 files changed, 17 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 37ad5fff0e04..45ebbcd76dc7 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -1,12 +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 System.Globalization; -using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.IO.Network; @@ -27,18 +24,17 @@ public abstract class APIRequest : APIRequest where T : class /// /// The deserialised response object. May be null if the request or deserialisation failed. /// - [CanBeNull] - public T Response { get; private set; } + public T? Response { get; private set; } /// /// Invoked on successful completion of an API request. /// This will be scheduled to the API's internal scheduler (run on update thread automatically). /// - public new event APISuccessHandler Success; + public new event APISuccessHandler? Success; protected APIRequest() { - base.Success += () => Success?.Invoke(Response); + base.Success += () => Success?.Invoke(Response!); } protected override void PostProcess() @@ -72,28 +68,28 @@ public abstract class APIRequest protected virtual WebRequest CreateWebRequest() => new OsuWebRequest(Uri); - protected virtual string Uri => $@"{API.APIEndpointUrl}/api/v2/{Target}"; + protected virtual string Uri => $@"{API!.APIEndpointUrl}/api/v2/{Target}"; - protected IAPIProvider API; + protected IAPIProvider? API; - protected WebRequest WebRequest; + protected WebRequest? WebRequest; /// /// The currently logged in user. Note that this will only be populated during . /// - protected APIUser User { get; private set; } + protected APIUser? User { get; private set; } /// /// Invoked on successful completion of an API request. /// This will be scheduled to the API's internal scheduler (run on update thread automatically). /// - public event APISuccessHandler Success; + public event APISuccessHandler? Success; /// /// Invoked on failure to complete an API request. /// This will be scheduled to the API's internal scheduler (run on update thread automatically). /// - public event APIFailureHandler Failure; + public event APIFailureHandler? Failure; private readonly object completionStateLock = new object(); @@ -210,7 +206,7 @@ public void Fail(Exception e) // in the case of a cancellation we don't care about whether there's an error in the response. if (!(e is OperationCanceledException)) { - string responseString = WebRequest?.GetResponseString(); + string? responseString = WebRequest?.GetResponseString(); // naive check whether there's an error in the response to avoid unnecessary JSON deserialisation. if (!string.IsNullOrEmpty(responseString) && responseString.Contains(@"""error""")) @@ -248,7 +244,7 @@ private bool isFailing private class DisplayableError { [JsonProperty("error")] - public string ErrorMessage { get; set; } + public string ErrorMessage { get; set; } = string.Empty; } } diff --git a/osu.Game/Online/API/Requests/JoinChannelRequest.cs b/osu.Game/Online/API/Requests/JoinChannelRequest.cs index 33eab7e3551e..0109e653d90d 100644 --- a/osu.Game/Online/API/Requests/JoinChannelRequest.cs +++ b/osu.Game/Online/API/Requests/JoinChannelRequest.cs @@ -23,6 +23,6 @@ protected override WebRequest CreateWebRequest() return req; } - protected override string Target => $@"chat/channels/{channel.Id}/users/{User.Id}"; + protected override string Target => $@"chat/channels/{channel.Id}/users/{User!.Id}"; } } diff --git a/osu.Game/Online/API/Requests/LeaveChannelRequest.cs b/osu.Game/Online/API/Requests/LeaveChannelRequest.cs index 7dfc9a0aed36..36cfd79c6074 100644 --- a/osu.Game/Online/API/Requests/LeaveChannelRequest.cs +++ b/osu.Game/Online/API/Requests/LeaveChannelRequest.cs @@ -23,6 +23,6 @@ protected override WebRequest CreateWebRequest() return req; } - protected override string Target => $@"chat/channels/{channel.Id}/users/{User.Id}"; + protected override string Target => $@"chat/channels/{channel.Id}/users/{User!.Id}"; } } diff --git a/osu.Game/Online/Chat/WebSocketChatClient.cs b/osu.Game/Online/Chat/WebSocketChatClient.cs index 37774a1f5da4..a74f0222f28e 100644 --- a/osu.Game/Online/Chat/WebSocketChatClient.cs +++ b/osu.Game/Online/Chat/WebSocketChatClient.cs @@ -80,7 +80,7 @@ public void RequestPresence() fetchReq.Success += updates => { - if (updates?.Presence != null) + if (updates.Presence != null) { foreach (var channel in updates.Presence) joinChannel(channel); diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index 8645f2a2c095..9a73104b608f 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -27,6 +27,6 @@ protected override WebRequest CreateWebRequest() return req; } - protected override string Target => $@"rooms/{Room.RoomID.Value}/users/{User.Id}"; + protected override string Target => $@"rooms/{Room.RoomID.Value}/users/{User!.Id}"; } } diff --git a/osu.Game/Online/Rooms/PartRoomRequest.cs b/osu.Game/Online/Rooms/PartRoomRequest.cs index 09ba6f65c3b5..2416833a1e8a 100644 --- a/osu.Game/Online/Rooms/PartRoomRequest.cs +++ b/osu.Game/Online/Rooms/PartRoomRequest.cs @@ -23,6 +23,6 @@ protected override WebRequest CreateWebRequest() return req; } - protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}"; + protected override string Target => $"rooms/{room.RoomID.Value}/users/{User!.Id}"; } } diff --git a/osu.Game/Tests/PollingChatClient.cs b/osu.Game/Tests/PollingChatClient.cs index eb29b35c1d91..75975c716b34 100644 --- a/osu.Game/Tests/PollingChatClient.cs +++ b/osu.Game/Tests/PollingChatClient.cs @@ -48,7 +48,7 @@ protected APIRequest CreateInitialFetchRequest(long? lastMessageId = null) fetchReq.Success += updates => { - if (updates?.Presence != null) + if (updates.Presence != null) { foreach (var channel in updates.Presence) handleChannelJoined(channel); From 291dd5b1016081e534b78e0d894a688bd9dec74a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:37:27 +0900 Subject: [PATCH 455/579] Remove TODO --- osu.Game/Screens/Select/BeatmapCarousel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7f6921d768db..32f85824fa0e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -320,7 +320,6 @@ private void processBeatmapChanges() { try { - // TODO: chekc whether we still need beatmap sets by ID foreach (var set in setsRequiringRemoval) removeBeatmapSet(set.ID); foreach (var set in setsRequiringUpdate) updateBeatmapSet(set); From 1b9942cb3092b7f5a3f256e611d1e33c4455a76d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:44:04 +0900 Subject: [PATCH 456/579] Mark `BeatmapSets` as `internal` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 32f85824fa0e..ed3fbc40545d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -122,7 +122,7 @@ public partial class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler beatmapSets => root.Items.OfType(); - public IEnumerable BeatmapSets + internal IEnumerable BeatmapSets { get => beatmapSets.Select(g => g.BeatmapSet); set From 2033a5e1579ff8cd3264b9f65c148ea700005929 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:44:51 +0900 Subject: [PATCH 457/579] Add disposal of `ManualResetEventSlim` --- osu.Game/Database/DetachedBeatmapStore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index 17d2dd15b603..7920f24a0b34 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -133,7 +133,9 @@ protected override void Update() protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + loaded.Set(); + loaded.Dispose(); realmSubscription?.Dispose(); } From de208fd5c385fe128968eeab4f182dfaf4c3abd3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:45:45 +0900 Subject: [PATCH 458/579] Add very basic error handling for failed beatmap detach --- osu.Game/Database/DetachedBeatmapStore.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index 7920f24a0b34..64aeeccd9a28 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; using Realms; namespace osu.Game.Database @@ -59,15 +60,21 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS Task.Factory.StartNew(() => { - realm.Run(_ => + try + { + realm.Run(_ => + { + var detached = frozenSets.Detach(); + + detachedBeatmapSets.Clear(); + detachedBeatmapSets.AddRange(detached); + }); + } + finally { - var detached = frozenSets.Detach(); - - detachedBeatmapSets.Clear(); - detachedBeatmapSets.AddRange(detached); loaded.Set(); - }); - }, TaskCreationOptions.LongRunning); + } + }, TaskCreationOptions.LongRunning).FireAndForget(); return; } From 7b6e62283ffa0a56a99581863b74735957ebacca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2024 18:50:00 +0900 Subject: [PATCH 459/579] Fix beatmap not being detached on hide/unhide The explicit detach call was removed from `updateBeatmapSet`, causing this to occur. We could optionally add it back (it will be a noop in all cases though). --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ed3fbc40545d..87cea45e874b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -391,7 +391,7 @@ private void beatmapsChanged(IRealmCollection sender, ChangeSet? ch if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSets) && existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID)) { - updateBeatmapSet(beatmapSet); + updateBeatmapSet(beatmapSet.Detach()); changed = true; } } From 8ffd4aa82c5e1afd4b4fe56773d6043248b54a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Aug 2024 13:41:34 +0200 Subject: [PATCH 460/579] Fix NRT inspections --- .../TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/API/APIDownloadRequest.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 585fd516bd7b..ae3451c3e0ec 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -257,7 +257,7 @@ public TestDownloadRequest(IBeatmapSetInfo model) { } - protected override string Target => null; + protected override string Target => string.Empty; } } } diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index c48372278abe..f8db52139d7c 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Diagnostics; using System.IO; using osu.Framework.IO.Network; @@ -34,7 +35,11 @@ protected override WebRequest CreateWebRequest() return request; } - private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total)); + private void request_Progress(long current, long total) + { + Debug.Assert(API != null); + API.Schedule(() => Progressed?.Invoke(current, total)); + } protected void TriggerSuccess(string filename) { From 8b04455c29fd41dfab49974f8883acb7ef60d8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Aug 2024 14:57:15 +0200 Subject: [PATCH 461/579] Fix chat overlay tests Not entirely sure why they were failing previously, but the most likely explanation is that by freak accident some mock requests would previously execute immediately rather than be scheduled on the API thread, which would change execution ordering and ensure that `ChannelManager.CurrentChannel` would become the joined channel, rather than remaining at the channel listing. --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index a47205094e7e..b6445dec6b53 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -446,7 +446,7 @@ public void TestKeyboardCloseAndRestoreChannel() { AddStep("Show overlay with channel 1", () => { - channelManager.JoinChannel(testChannel1); + channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel1); chatOverlay.Show(); }); waitForChannel1Visible(); @@ -462,7 +462,7 @@ public void TestKeyboardNewChannel() { AddStep("Show overlay with channel 1", () => { - channelManager.JoinChannel(testChannel1); + channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel1); chatOverlay.Show(); }); waitForChannel1Visible(); From f5a2b5ea03caa71e4122926ccea6d0b53e1b781f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Aug 2024 02:20:11 +0300 Subject: [PATCH 462/579] Use FastCircle in demanding places in the editor --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 4 ++-- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 2 +- .../Timelines/Summary/Visualisations/PointVisualisation.cs | 2 +- osu.Game/Screens/Loader.cs | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 9d819f6cc088..3337e9921579 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -40,7 +40,7 @@ public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip public readonly PathControlPoint ControlPoint; private readonly T hitObject; - private readonly Circle circle; + private readonly FastCircle circle; private readonly Drawable markerRing; [Resolved] @@ -62,7 +62,7 @@ public PathControlPointPiece(T hitObject, PathControlPoint controlPoint) InternalChildren = new[] { - circle = new Circle + circle = new FastCircle { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index 17fedb933a89..1d71bc100cda 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -105,7 +105,7 @@ private void refreshDisplay() } } - private partial class KiaiVisualisation : Circle, IHasTooltip + private partial class KiaiVisualisation : FastCircle, IHasTooltip { private readonly double startTime; private readonly double endTime; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 9c16f457f751..6c9af5396471 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// /// Represents a singular point on a timeline part. /// - public partial class PointVisualisation : Circle + public partial class PointVisualisation : FastCircle { public readonly double StartTime; diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 4dba512cbd15..57e3998646f6 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -122,6 +122,7 @@ private void load(ShaderManager manager) loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder")); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "FastCircle")); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); } From 225418dbb36db38ac9d97c7b7bd960380018be4f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2024 01:59:40 +0300 Subject: [PATCH 463/579] Rework kiai handling in summary timeline --- .../Summary/Parts/EffectPointVisualisation.cs | 93 +------------ .../Timelines/Summary/Parts/KiaiPart.cs | 123 ++++++++++++++++++ .../Timelines/Summary/SummaryTimeline.cs | 6 + 3 files changed, 130 insertions(+), 92 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index b4e6d1ece2f9..25d50a97be17 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -2,29 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Localisation; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Extensions; -using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public partial class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisation { private readonly EffectControlPoint effect; - private Bindable kiai = null!; [Resolved] private EditorBeatmap beatmap { get; set; } = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - public EffectPointVisualisation(EffectControlPoint point) { RelativePositionAxes = Axes.Both; @@ -36,49 +26,6 @@ public EffectPointVisualisation(EffectControlPoint point) [BackgroundDependencyLoader] private void load() { - kiai = effect.KiaiModeBindable.GetBoundCopy(); - kiai.BindValueChanged(_ => refreshDisplay(), true); - } - - private EffectControlPoint? nextControlPoint; - - protected override void LoadComplete() - { - base.LoadComplete(); - - // Due to the limitations of ControlPointInfo, it's impossible to know via event flow when the next kiai point has changed. - // This is due to the fact that an EffectPoint can be added to an existing group. We would need to bind to ItemAdded on *every* - // future group to track this. - // - // I foresee this being a potential performance issue on beatmaps with many control points, so let's limit how often we check - // for changes. ControlPointInfo needs a refactor to make this flow better, but it should do for now. - Scheduler.AddDelayed(() => - { - EffectControlPoint? next = null; - - for (int i = 0; i < beatmap.ControlPointInfo.EffectPoints.Count; i++) - { - var point = beatmap.ControlPointInfo.EffectPoints[i]; - - if (point.Time > effect.Time) - { - next = point; - break; - } - } - - if (!ReferenceEquals(nextControlPoint, next)) - { - nextControlPoint = next; - refreshDisplay(); - } - }, 100, true); - } - - private void refreshDisplay() - { - ClearInternal(); - if (beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed) { AddInternal(new ControlPointVisualisation(effect) @@ -87,46 +34,8 @@ private void refreshDisplay() X = 0, }); } - - if (!kiai.Value) - return; - - // handle kiai duration - // eventually this will be simpler when we have control points with durations. - if (nextControlPoint != null) - { - RelativeSizeAxes = Axes.Both; - Origin = Anchor.TopLeft; - - Width = (float)(nextControlPoint.Time - effect.Time); - - AddInternal(new KiaiVisualisation(effect.Time, nextControlPoint.Time) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomLeft, - Origin = Anchor.CentreLeft, - Height = 0.4f, - Depth = float.MaxValue, - Colour = colours.Purple1, - }); - } - } - - private partial class KiaiVisualisation : Circle, IHasTooltip - { - private readonly double startTime; - private readonly double endTime; - - public KiaiVisualisation(double startTime, double endTime) - { - this.startTime = startTime; - this.endTime = endTime; - } - - public LocalisableString TooltipText => $"{startTime.ToEditorFormattedString()} - {endTime.ToEditorFormattedString()} kiai time"; } - // kiai sections display duration, so are required to be visualised. - public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode; + public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs new file mode 100644 index 000000000000..d61d4580fedd --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs @@ -0,0 +1,123 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Extensions; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays kiai sections in the song. + /// + public partial class KiaiPart : TimelinePart + { + private DrawablePool pool = null!; + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(pool = new DrawablePool(10)); + } + + protected override void LoadBeatmap(EditorBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + EditorBeatmap.ControlPointInfo.ControlPointsChanged += updateParts; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateParts(); + } + + private void updateParts() => Scheduler.AddOnce(() => + { + Clear(disposeChildren: false); + + double? startTime = null; + + foreach (var effectPoint in EditorBeatmap.ControlPointInfo.EffectPoints) + { + if (startTime.HasValue) + { + if (effectPoint.KiaiMode) + continue; + + var section = new KiaiSection + { + StartTime = startTime.Value, + EndTime = effectPoint.Time + }; + + Add(pool.Get(v => v.Section = section)); + + startTime = null; + } + else + { + if (!effectPoint.KiaiMode) + continue; + + startTime = effectPoint.Time; + } + } + + // last effect point has kiai enabled, kiai should last until the end of the map + if (startTime.HasValue) + { + Add(pool.Get(v => v.Section = new KiaiSection + { + StartTime = startTime.Value, + EndTime = Content.RelativeChildSize.X + })); + } + }); + + private partial class KiaiVisualisation : PoolableDrawable, IHasTooltip + { + private KiaiSection section; + + public KiaiSection Section + { + set + { + section = value; + + X = (float)value.StartTime; + Width = (float)value.Duration; + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + Height = 0.2f; + AddInternal(new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Purple1 + }); + } + + public LocalisableString TooltipText => $"{section.StartTime.ToEditorFormattedString()} - {section.EndTime.ToEditorFormattedString()} kiai time"; + } + + private readonly struct KiaiSection + { + public double StartTime { get; init; } + public double EndTime { get; init; } + public double Duration => EndTime - StartTime; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 4ab7c8817821..c01481e840a7 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -65,6 +65,12 @@ private void load(OverlayColourProvider colourProvider) Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, }, + new KiaiPart + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, new ControlPointPart { Anchor = Anchor.Centre, From 6b8b49e4f181cdf0feed5952d5a8f50f583ebd71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2024 13:14:56 +0900 Subject: [PATCH 464/579] Simplify scroll speed point display code now that it only serves one purpose --- .../Summary/Parts/EffectPointVisualisation.cs | 41 ------------------- .../Summary/Parts/GroupVisualisation.cs | 21 ++++++++-- 2 files changed, 18 insertions(+), 44 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs deleted file mode 100644 index 25d50a97be17..000000000000 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts -{ - public partial class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisation - { - private readonly EffectControlPoint effect; - - [Resolved] - private EditorBeatmap beatmap { get; set; } = null!; - - public EffectPointVisualisation(EffectControlPoint point) - { - RelativePositionAxes = Axes.Both; - RelativeSizeAxes = Axes.Y; - - effect = point; - } - - [BackgroundDependencyLoader] - private void load() - { - if (beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed) - { - AddInternal(new ControlPointVisualisation(effect) - { - // importantly, override the x position being set since we do that in the GroupVisualisation parent drawable. - X = 0, - }); - } - } - - public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint; - } -} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index b872c3725c15..0dd945805b80 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,6 +16,8 @@ public partial class GroupVisualisation : CompositeDrawable private readonly IBindableList controlPoints = new BindableList(); + private bool showScrollSpeed; + public GroupVisualisation(ControlPointGroup group) { RelativePositionAxes = Axes.X; @@ -24,8 +27,13 @@ public GroupVisualisation(ControlPointGroup group) Group = group; X = (float)group.Time; + } + + [BackgroundDependencyLoader] + private void load(EditorBeatmap beatmap) + { + showScrollSpeed = beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed; - // Run in constructor so IsRedundant calls can work correctly. controlPoints.BindTo(Group.ControlPoints); controlPoints.BindCollectionChanged((_, _) => { @@ -47,8 +55,15 @@ public GroupVisualisation(ControlPointGroup group) }); break; - case EffectControlPoint effect: - AddInternal(new EffectPointVisualisation(effect)); + case EffectControlPoint: + if (!showScrollSpeed) + return; + + AddInternal(new ControlPointVisualisation(point) + { + // importantly, override the x position being set since we do that above. + X = 0, + }); break; } } From 636ee50eb9cbdc2d5d75de884b4be79ddd914e45 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 31 Aug 2024 23:03:10 +0900 Subject: [PATCH 465/579] Rename to VelopackUpdateManager --- osu.Desktop/OsuGameDesktop.cs | 2 +- .../{VeloUpdateManager.cs => VelopackUpdateManager.cs} | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) rename osu.Desktop/Updater/{VeloUpdateManager.cs => VelopackUpdateManager.cs} (96%) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index ee73c84ba372..94f6ef0fc349 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -100,7 +100,7 @@ protected override UpdateManager CreateUpdateManager() if (!string.IsNullOrEmpty(packageManaged)) return new NoActionUpdateManager(); - return new VeloUpdateManager(); + return new VelopackUpdateManager(); } public override bool RestartAppWhenExited() diff --git a/osu.Desktop/Updater/VeloUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs similarity index 96% rename from osu.Desktop/Updater/VeloUpdateManager.cs rename to osu.Desktop/Updater/VelopackUpdateManager.cs index 6d3eb3f3f069..5cdc87e5392f 100644 --- a/osu.Desktop/Updater/VeloUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -11,11 +11,10 @@ using osu.Game.Screens.Play; using Velopack; using Velopack.Sources; -using UpdateManager = Velopack.UpdateManager; namespace osu.Desktop.Updater { - public partial class VeloUpdateManager : Game.Updater.UpdateManager + public partial class VelopackUpdateManager : Game.Updater.UpdateManager { private readonly UpdateManager updateManager; private INotificationOverlay notificationOverlay = null!; @@ -26,7 +25,7 @@ public partial class VeloUpdateManager : Game.Updater.UpdateManager [Resolved] private ILocalUserPlayInfo? localUserInfo { get; set; } - public VeloUpdateManager() + public VelopackUpdateManager() { const string? github_token = null; // TODO: populate. updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), new UpdateOptions From 837fa1b8dc397c370bd43bc640610405f6fcffc9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2024 17:32:24 +0300 Subject: [PATCH 466/579] Use FastCircle for kiai visualisation --- .../Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs index d61d4580fedd..ee44df8598d0 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/KiaiPart.cs @@ -103,7 +103,7 @@ private void load(OsuColour colours) Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; Height = 0.2f; - AddInternal(new Circle + AddInternal(new FastCircle { RelativeSizeAxes = Axes.Both, Colour = colours.Purple1 From a038799c4745bf8f47189d13beec664f708785ac Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Sat, 31 Aug 2024 17:14:53 -0400 Subject: [PATCH 467/579] Update osu.Desktop.csproj bump version --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index ffa0c30b0c83..3588317b8a8b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,7 +25,7 @@ - + From f7da7193ff683c5fb7b9ced964fff3090e2b00af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Sep 2024 19:10:08 +0900 Subject: [PATCH 468/579] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index b5a355a77f01..2609fd42c36e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index a94b9375c976..1056f4b44174 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From b990af6adad67f239c31f3cc28ccfe657c9428d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 13:08:14 +0900 Subject: [PATCH 469/579] Use full name --- osu.Desktop/Program.cs | 4 ++-- osu.sln.DotSettings | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 92c8f2104c08..609af2a8a759 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -31,7 +31,7 @@ public static class Program public static void Main(string[] args) { // Velopack needs to run before anything else - setupVelo(); + setupVelopack(); if (OperatingSystem.IsWindows()) { @@ -164,7 +164,7 @@ private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args) return false; } - private static void setupVelo() + private static void setupVelopack() { VelopackApp .Build() diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index a792b956dd41..38686d85089f 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -1060,5 +1060,6 @@ private void load() True True True + True True True From 42e1168b35072115839e266ada4baebb9cfb6915 Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Mon, 2 Sep 2024 01:23:05 -0400 Subject: [PATCH 470/579] Remove github token variable & pass null for the github token --- osu.Desktop/Updater/VelopackUpdateManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index 5cdc87e5392f..eb1463483fc9 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -27,8 +27,7 @@ public partial class VelopackUpdateManager : Game.Updater.UpdateManager public VelopackUpdateManager() { - const string? github_token = null; // TODO: populate. - updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), new UpdateOptions + updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", null, false), new UpdateOptions { AllowVersionDowngrade = true }); From 30fb3c3999c9b5a230865377a8721bef6e54545b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 2 Sep 2024 15:23:40 +0900 Subject: [PATCH 471/579] Fix osu!catch fruits not resizing on texture change --- .../Legacy/LegacyCatchHitObjectPiece.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchHitObjectPiece.cs index 2184ecc3637d..15b168b8c2d2 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchHitObjectPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchHitObjectPiece.cs @@ -85,9 +85,25 @@ protected override void LoadComplete() protected void SetTexture(Texture? texture, Texture? overlayTexture) { - colouredSprite.Texture = texture; - overlaySprite.Texture = overlayTexture; - hyperSprite.Texture = texture; + // Sizes are reset due to an arguable osu!framework bug where Sprite retains the size of the first set texture. + + if (colouredSprite.Texture != texture) + { + colouredSprite.Size = Vector2.Zero; + colouredSprite.Texture = texture; + } + + if (overlaySprite.Texture != overlayTexture) + { + overlaySprite.Size = Vector2.Zero; + overlaySprite.Texture = overlayTexture; + } + + if (hyperSprite.Texture != texture) + { + hyperSprite.Size = Vector2.Zero; + hyperSprite.Texture = texture; + } } } } From 38a62eed4458ea0897105537e5cfc28830798019 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 16:29:41 +0900 Subject: [PATCH 472/579] Add automatic downloading support when spectating a multiplayer room --- .../Match/MultiplayerSpectateButton.cs | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 1d308ed39cb5..8bc37042614d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -3,12 +3,19 @@ #nullable disable +using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -46,10 +53,11 @@ private void onClick() } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy(); operationInProgress.BindValueChanged(_ => updateState()); + automaticallyDownload = config.GetBindable(OsuSetting.AutomaticallyDownloadMissingBeatmaps); } protected override void OnRoomUpdated() @@ -77,6 +85,70 @@ private void updateState() button.Enabled.Value = Client.Room != null && Client.Room.State != MultiplayerRoomState.Closed && !operationInProgress.Value; + + Scheduler.AddOnce(checkForAutomaticDownload); + } + + #region Automatic download handling + + [Resolved] + private BeatmapLookupCache beatmapLookupCache { get; set; } + + [Resolved] + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + private CancellationTokenSource downloadCheckCancellation; + + private Bindable automaticallyDownload; + + protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) + { + base.PlaylistItemChanged(item); + Scheduler.AddOnce(checkForAutomaticDownload); } + + private void checkForAutomaticDownload() + { + MultiplayerPlaylistItem item = Client.Room?.Playlist.FirstOrDefault(i => !i.Expired); + + downloadCheckCancellation?.Cancel(); + + if (item == null) + return; + + if (!automaticallyDownload.Value) + return; + + // While we can support automatic downloads when not spectating, there are some usability concerns. + // - In host rotate mode, this could potentially be unwanted by some users (even though they want automatic downloads everywhere else). + // - When first joining a room, the expectation should be that the user is checking out the room, and they may not immediately want to download the selected beatmap. + // + // Rather than over-complicating this flow, let's only auto-download when spectating for the time being. + // A potential path forward would be to have a local auto-download checkbox above the playlist item list area. + if (Client.LocalUser?.State != MultiplayerUserState.Spectating) + return; + + // In a perfect world we'd use BeatmapAvailability, but there's no event-driven flow for when a selection changes. + // ie. if selection changes from "not downloaded" to another "not downloaded" we wouldn't get a value changed raised. + beatmapLookupCache + .GetBeatmapAsync(item.BeatmapID, (downloadCheckCancellation = new CancellationTokenSource()).Token) + .ContinueWith(resolved => Schedule(() => + { + var beatmapSet = resolved.GetResultSafely()?.BeatmapSet; + + if (beatmapSet == null) + return; + + if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID })) + return; + + beatmapDownloader.Download(beatmapSet); + })); + } + + #endregion } } From 6227e4f848ce0fc24c54cd81d47e9bb9268242d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 16:35:10 +0900 Subject: [PATCH 473/579] Apply NRT to `MultiplayerSpectateButton` --- .../Match/MultiplayerSpectateButton.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 8bc37042614d..bb6cd6cdaa5b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.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 System.Threading; using osu.Framework.Allocation; @@ -23,12 +21,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public partial class MultiplayerSpectateButton : MultiplayerRoomComposite { [Resolved] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - private IBindable operationInProgress; + private IBindable operationInProgress = null!; private readonly RoundedButton button; @@ -92,17 +90,17 @@ private void updateState() #region Automatic download handling [Resolved] - private BeatmapLookupCache beatmapLookupCache { get; set; } + private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; [Resolved] private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; - private CancellationTokenSource downloadCheckCancellation; + private Bindable automaticallyDownload = null!; - private Bindable automaticallyDownload; + private CancellationTokenSource? downloadCheckCancellation; protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) { @@ -112,7 +110,7 @@ protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) private void checkForAutomaticDownload() { - MultiplayerPlaylistItem item = Client.Room?.Playlist.FirstOrDefault(i => !i.Expired); + MultiplayerPlaylistItem? item = Client.Room?.Playlist.FirstOrDefault(i => !i.Expired); downloadCheckCancellation?.Cancel(); From f8a6a6a8aef5db35a3d5cdf7aca50247d4cfedd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 16:43:46 +0900 Subject: [PATCH 474/579] Request restart asynchronously to avoid blocking update thread --- osu.Desktop/OsuGameDesktop.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 94f6ef0fc349..c75a3f0a1af5 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -5,6 +5,7 @@ using System.IO; using System.Reflection; using System.Runtime.Versioning; +using System.Threading.Tasks; using Microsoft.Win32; using osu.Desktop.Performance; using osu.Desktop.Security; @@ -18,6 +19,7 @@ using osu.Framework.Allocation; using osu.Game.IO; using osu.Game.IPC; +using osu.Game.Online.Multiplayer; using osu.Game.Performance; using osu.Game.Utils; @@ -105,16 +107,8 @@ protected override UpdateManager CreateUpdateManager() public override bool RestartAppWhenExited() { - try - { - Velopack.UpdateExe.Start(); - return true; - } - catch (Exception e) - { - Logger.Error(e, "Failed to restart application"); - return base.RestartAppWhenExited(); - } + Task.Run(() => Velopack.UpdateExe.Start()).FireAndForget(); + return true; } protected override void LoadComplete() From 68e6fa286e00c0fef777d5d2fcfa49f503a5bd45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 16:46:29 +0900 Subject: [PATCH 475/579] Make comment about velopack's init a bit more loud --- osu.Desktop/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 609af2a8a759..510366381501 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -30,7 +30,9 @@ public static class Program [STAThread] public static void Main(string[] args) { - // Velopack needs to run before anything else + // IMPORTANT DON'T IGNORE: For general sanity, velopack's setup needs to run before anything else. + // This has bitten us in the rear before (bricked updater), and although the underlying issue from + // last time has been fixed, let's not tempt fate. setupVelopack(); if (OperatingSystem.IsWindows()) From cd9b82253e4639d3a94dc049244c9ccbcc59ea47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 17:20:33 +0900 Subject: [PATCH 476/579] Pass through correct update to apply when calling `WaitExitThenApplyUpdates` --- osu.Desktop/Updater/VelopackUpdateManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index eb1463483fc9..4d2535ed32c3 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -52,7 +52,7 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific if (localUserInfo?.IsPlaying.Value == true) return false; - var info = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false); + UpdateInfo? info = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false); // Handle no updates available. if (info == null) @@ -65,7 +65,7 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific { Activated = () => { - restartToApplyUpdate(); + restartToApplyUpdate(null); return true; } }); @@ -78,7 +78,7 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific { notification = new UpdateProgressNotification { - CompletionClickAction = restartToApplyUpdate, + CompletionClickAction = () => restartToApplyUpdate(info), }; Schedule(() => notificationOverlay.Post(notification)); @@ -117,9 +117,9 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific return true; } - private bool restartToApplyUpdate() + private bool restartToApplyUpdate(UpdateInfo? info) { - updateManager.WaitExitThenApplyUpdates(null); + updateManager.WaitExitThenApplyUpdates(info?.TargetFullRelease); Schedule(() => game.AttemptExit()); return true; } From 171ac0f5104efea07c262cffa1622d6d758ed289 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 17:26:14 +0900 Subject: [PATCH 477/579] Fix incorrect osu!catch snap display when last object is a juice stream Addresses https://github.com/ppy/osu/discussions/29678. --- osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs index c3103bd2043c..6f5b32a41d51 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs @@ -18,7 +18,9 @@ protected override double ReadCurrentDistanceSnap(HitObject before, HitObject af // The implementation below is probably correct but should be checked if/when exposed via controls. float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime()); - float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX); + + float previousEndX = (before as JuiceStream)?.EndX ?? ((CatchHitObject)before).EffectiveX; + float actualDistance = Math.Abs(previousEndX - ((CatchHitObject)after).EffectiveX); return actualDistance / expectedDistance; } From 10901075bef07ba3abba1a22c0166a5bcd23c6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2024 11:27:44 +0200 Subject: [PATCH 478/579] Modify existing test coverage to demonstrate failure with touch --- .../TestSceneModCustomisationPanel.cs | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index d93f62935a1a..04cb12963024 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; @@ -120,7 +121,7 @@ public void TestHoverExpandsWithCustomisableMods() } [Test] - public void TestExpandedStatePersistsWhenClicked() + public void TestHoverExpandsAndCollapsesWhenHeaderTouched() { AddStep("add customisable mod", () => { @@ -128,34 +129,20 @@ public void TestExpandedStatePersistsWhenClicked() panel.Enabled.Value = true; }); - AddStep("hover header", () => InputManager.MoveMouseTo(header)); - checkExpanded(true); - - AddStep("click", () => InputManager.Click(MouseButton.Left)); - checkExpanded(false); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - checkExpanded(true); - - AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One)); - checkExpanded(true); - - AddStep("click", () => InputManager.Click(MouseButton.Left)); - checkExpanded(false); - } - - [Test] - public void TestHoverExpandsAndCollapsesWhenHeaderClicked() - { - AddStep("add customisable mod", () => + AddStep("touch header", () => { - SelectedMods.Value = new[] { new OsuModDoubleTime() }; - panel.Enabled.Value = true; + var touch = new Touch(TouchSource.Touch1, header.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + Schedule(() => InputManager.EndTouch(touch)); }); - - AddStep("hover header", () => InputManager.MoveMouseTo(header)); checkExpanded(true); - AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("touch away from header", () => + { + var touch = new Touch(TouchSource.Touch1, header.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)); + InputManager.BeginTouch(touch); + Schedule(() => InputManager.EndTouch(touch)); + }); checkExpanded(false); } From 5211e606b5b0c9919a2675c09a263ec88acb8c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2024 11:33:43 +0200 Subject: [PATCH 479/579] Fix mod customisation header being non-functional on mobile As expected the previous touch handling would prevent the header from working entirely when click handling was removed from the header. --- .../Overlays/Mods/ModCustomisationHeader.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 5ddcf01b88be..32fd5a37aa54 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -112,26 +112,10 @@ protected override void LoadComplete() }, true); } - private bool touchedThisFrame; - - protected override bool OnTouchDown(TouchDownEvent e) - { - if (Enabled.Value) - { - touchedThisFrame = true; - Schedule(() => touchedThisFrame = false); - } - - return base.OnTouchDown(e); - } - protected override bool OnHover(HoverEvent e) { - if (Enabled.Value) - { - if (!touchedThisFrame && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed) - panel.ExpandedState.Value = ModCustomisationPanelState.Expanded; - } + if (Enabled.Value && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed) + panel.ExpandedState.Value = ModCustomisationPanelState.Expanded; return base.OnHover(e); } From 872d14ed88b8daf04121cd1a5483bbf8cb5dbd79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 19:18:43 +0900 Subject: [PATCH 480/579] Fix incorrect handling of ordered playlist items --- .../Match/MultiplayerSpectateButton.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index bb6cd6cdaa5b..fa26a8578628 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -58,6 +57,13 @@ private void load(OsuConfigManager config) automaticallyDownload = config.GetBindable(OsuSetting.AutomaticallyDownloadMissingBeatmaps); } + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentPlaylistItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true); + } + protected override void OnRoomUpdated() { base.OnRoomUpdated(); @@ -102,19 +108,13 @@ private void updateState() private CancellationTokenSource? downloadCheckCancellation; - protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) - { - base.PlaylistItemChanged(item); - Scheduler.AddOnce(checkForAutomaticDownload); - } - private void checkForAutomaticDownload() { - MultiplayerPlaylistItem? item = Client.Room?.Playlist.FirstOrDefault(i => !i.Expired); + PlaylistItem? currentItem = CurrentPlaylistItem.Value; downloadCheckCancellation?.Cancel(); - if (item == null) + if (currentItem == null) return; if (!automaticallyDownload.Value) @@ -132,7 +132,7 @@ private void checkForAutomaticDownload() // In a perfect world we'd use BeatmapAvailability, but there's no event-driven flow for when a selection changes. // ie. if selection changes from "not downloaded" to another "not downloaded" we wouldn't get a value changed raised. beatmapLookupCache - .GetBeatmapAsync(item.BeatmapID, (downloadCheckCancellation = new CancellationTokenSource()).Token) + .GetBeatmapAsync(currentItem.Beatmap.OnlineID, (downloadCheckCancellation = new CancellationTokenSource()).Token) .ContinueWith(resolved => Schedule(() => { var beatmapSet = resolved.GetResultSafely()?.BeatmapSet; From 7b139433772804c5c5f044abc57589e9cf638af5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2024 19:20:05 +0900 Subject: [PATCH 481/579] Handle changes to the automatic download setting immediately --- .../OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index fa26a8578628..ea7ab2dce34d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -54,7 +54,9 @@ private void load(OsuConfigManager config) { operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy(); operationInProgress.BindValueChanged(_ => updateState()); + automaticallyDownload = config.GetBindable(OsuSetting.AutomaticallyDownloadMissingBeatmaps); + automaticallyDownload.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload)); } protected override void LoadComplete() From 16c2c140377286c2a90430d23a37e5c7da43cc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2024 14:45:29 +0200 Subject: [PATCH 482/579] Adjust tests further to match new UX --- .../TestSceneModSelectOverlay.cs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index f21c64f7fea7..280497e861d9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -241,12 +241,8 @@ public void TestCustomisationToggleState() AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick()); assertCustomisationToggleState(disabled: false, active: true); - AddStep("dismiss mod customisation via toggle", () => - { - InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - assertCustomisationToggleState(disabled: false, active: false); + AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero)); + assertCustomisationToggleState(disabled: false, active: true); AddStep("reset mods", () => SelectedMods.SetDefault()); AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick()); @@ -664,7 +660,7 @@ public void TestTextSearchDoesNotBlockCustomisationPanelKeyboardInteractions() AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); AddAssert("DT selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value), () => Is.EqualTo(1)); - AddStep("open customisation area", () => modSelectOverlay.ChildrenOfType().Single().TriggerClick()); + AddStep("open customisation area", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().Single())); assertCustomisationToggleState(disabled: false, active: true); AddStep("hover over mod settings slider", () => @@ -976,7 +972,7 @@ public void TestCustomisationPanelAbsorbsInput([Values] bool textSearchStartsAct createScreen(); AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); - AddStep("open customisation panel", () => this.ChildrenOfType().Single().TriggerClick()); + AddStep("open customisation panel", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); AddAssert("search lost focus", () => !this.ChildrenOfType().Single().HasFocus); AddStep("press tab", () => InputManager.Key(Key.Tab)); @@ -991,15 +987,13 @@ public void TestCustomisationPanelAbsorbsInput([Values] bool textSearchStartsAct AddStep("press backspace", () => InputManager.Key(Key.BackSpace)); AddAssert("mods not deselected", () => SelectedMods.Value.Single() is OsuModDoubleTime); - AddStep("move mouse to scroll bar", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().Single().ScreenSpaceDrawQuad.BottomLeft + new Vector2(10f, -5f))); + AddStep("move mouse to customisation panel", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().First())); AddStep("scroll down", () => InputManager.ScrollVerticalBy(-10f)); AddAssert("column not scrolled", () => modSelectOverlay.ChildrenOfType().Single().IsScrolledToStart()); - AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left)); - AddAssert("search still not focused", () => !this.ChildrenOfType().Single().HasFocus); - AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("customisation panel closed by click", + AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddAssert("customisation panel closed", () => this.ChildrenOfType().Single().ExpandedState.Value, () => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); From e3457d850157808928b7ef912b8ecc562826f5a2 Mon Sep 17 00:00:00 2001 From: Fabep Date: Mon, 2 Sep 2024 19:14:08 +0200 Subject: [PATCH 483/579] Mod customisation header's color is now based on the state of the panel rather than the hover of the container. --- .../Overlays/Mods/ModCustomisationHeader.cs | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 32fd5a37aa54..d8191f5ba5b8 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -20,7 +19,7 @@ namespace osu.Game.Overlays.Mods { - public partial class ModCustomisationHeader : OsuHoverContainer + public partial class ModCustomisationHeader : OsuClickableContainer { private Box background = null!; private Box backgroundFlash = null!; @@ -29,8 +28,6 @@ public partial class ModCustomisationHeader : OsuHoverContainer [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - protected override IEnumerable EffectTargets => new[] { background }; - public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); private readonly ModCustomisationPanel panel; @@ -52,6 +49,7 @@ private void load() background = new Box { RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Dark3, }, backgroundFlash = new Box { @@ -84,9 +82,6 @@ private void load() } } }; - - IdleColour = colourProvider.Dark3; - HoverColour = colourProvider.Light4; } protected override void LoadComplete() @@ -110,6 +105,20 @@ protected override void LoadComplete() { icon.ScaleTo(v.NewValue > ModCustomisationPanelState.Collapsed ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); }, true); + + panel.ExpandedState.BindValueChanged(v => + { + switch (v.NewValue) + { + case ModCustomisationPanelState.Expanded: + case ModCustomisationPanelState.ExpandedByMod: + fadeBackgroundColor(colourProvider.Light4); + break; + default: + fadeBackgroundColor(colourProvider.Dark3); + break; + }; + }, false); } protected override bool OnHover(HoverEvent e) @@ -119,5 +128,10 @@ protected override bool OnHover(HoverEvent e) return base.OnHover(e); } + + private void fadeBackgroundColor(Color4 color) + { + background.FadeColour(color, 500, Easing.OutQuint); + } } } From 582ffcfc9722342f73d83778fc950cacc1bb9439 Mon Sep 17 00:00:00 2001 From: Fabep Date: Mon, 2 Sep 2024 19:17:07 +0200 Subject: [PATCH 484/579] Woopsie! I accidentally added one too many semi-colons, so I moved it here into the commit instead ; --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index d8191f5ba5b8..da77d8dac8cb 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -117,7 +117,7 @@ protected override void LoadComplete() default: fadeBackgroundColor(colourProvider.Dark3); break; - }; + } }, false); } From a2b15fcdee7feccabe605e06984c0b851843de31 Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Tue, 3 Sep 2024 00:59:42 -0400 Subject: [PATCH 485/579] rework code logic to make more sense analysis container creates settings and the settings items are created more nicely also removed use of localized strings because that can be done separately --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 +- .../UI/OsuAnalysisContainer.cs | 35 +++++++----- .../UI/OsuAnalysisSettings.cs | 53 ++++--------------- .../PlayerSettingsOverlayStrings.cs | 20 ------- osu.Game/Rulesets/Ruleset.cs | 4 +- osu.Game/Rulesets/UI/AnalysisContainer.cs | 13 ++++- .../Play/PlayerSettings/AnalysisSettings.cs | 13 ++--- osu.Game/Screens/Play/ReplayPlayer.cs | 8 +-- 8 files changed, 55 insertions(+), 95 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 8beeeac34e4c..a8a1d98bf3ad 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -13,6 +13,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays.Settings; +using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -37,7 +38,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; @@ -361,7 +361,7 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } - public override AnalysisSettings CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset); + public override OsuAnalysisContainer CreateAnalysisContainer(Replay replay, Playfield playfield) => new OsuAnalysisContainer(replay, playfield); public override bool EditorShowScrollSpeed => false; } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 7d8ae6980cda..3bddc479ef93 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -1,10 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. + +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; @@ -21,27 +21,33 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisContainer : AnalysisContainer { - public Bindable HitMarkerEnabled = new BindableBool(); - public Bindable AimMarkersEnabled = new BindableBool(); - public Bindable AimLinesEnabled = new BindableBool(); + public new OsuAnalysisSettings AnalysisSettings => (OsuAnalysisSettings)base.AnalysisSettings; + + protected new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; protected HitMarkersContainer HitMarkers; protected AimMarkersContainer AimMarkers; protected AimLinesContainer AimLines; - public OsuAnalysisContainer(Replay replay) - : base(replay) + public OsuAnalysisContainer(Replay replay, Playfield playfield) + : base(replay, playfield) { InternalChildren = new Drawable[] { + AimLines = new AimLinesContainer { Depth = float.MaxValue }, HitMarkers = new HitMarkersContainer(), - AimMarkers = new AimMarkersContainer { Depth = float.MinValue }, - AimLines = new AimLinesContainer { Depth = float.MaxValue } + AimMarkers = new AimMarkersContainer { Depth = float.MinValue } }; + } - HitMarkerEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0); - AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0); - AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0); + protected override OsuAnalysisSettings CreateAnalysisSettings() + { + var settings = new OsuAnalysisSettings(); + settings.HitMarkersEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0); + settings.AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0); + settings.AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0); + settings.CursorHideEnabled.ValueChanged += e => Playfield.Cursor.FadeTo(e.NewValue ? 0 : 1); + return settings; } [BackgroundDependencyLoader] @@ -51,6 +57,11 @@ private void load() AimMarkers.Hide(); AimLines.Hide(); + LoadReplay(); + } + + protected void LoadReplay() + { bool leftHeld = false; bool rightHeld = false; diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index b3d5231adec4..c45b893a1c6c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -2,58 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Localisation; -using osu.Game.Replays; -using osu.Game.Rulesets.UI; +using osu.Game.Configuration; using osu.Game.Screens.Play.PlayerSettings; namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { - protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset; + [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool HitMarkersEnabled { get; } = new BindableBool(); - private readonly PlayerCheckbox hitMarkerToggle; - private readonly PlayerCheckbox aimMarkerToggle; - private readonly PlayerCheckbox aimLinesToggle; + [SettingSource("Aim markers", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool AimMarkersEnabled { get; } = new BindableBool(); - public OsuAnalysisSettings(DrawableRuleset drawableRuleset) - : base(drawableRuleset) - { - PlayerCheckbox hideCursorToggle; + [SettingSource("Aim lines", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool AimLinesEnabled { get; } = new BindableBool(); - Children = new Drawable[] - { - hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, - aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, - aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, - hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } - }; - - hideCursorToggle.Current.BindValueChanged(onCursorToggle); - } - - private void onCursorToggle(ValueChangedEvent hide) - { - // this only hides half the cursor - if (hide.NewValue) - { - DrawableRuleset.Playfield.Cursor.FadeOut(); - } - else - { - DrawableRuleset.Playfield.Cursor.FadeIn(); - } - } - - public override AnalysisContainer CreateAnalysisContainer(Replay replay) - { - var analysisContainer = new OsuAnalysisContainer(replay); - analysisContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); - analysisContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); - analysisContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); - return analysisContainer; - } + [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool CursorHideEnabled { get; } = new BindableBool(); } } diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs index 017cc9bf82c4..60874da561ff 100644 --- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -19,26 +19,6 @@ public static class PlayerSettingsOverlayStrings /// public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame"); - /// - /// "Hit markers" - /// - public static LocalisableString HitMarkers => new TranslatableString(getKey(@"hit_markers"), @"Hit markers"); - - /// - /// "Aim markers" - /// - public static LocalisableString AimMarkers => new TranslatableString(getKey(@"aim_markers"), @"Aim markers"); - - /// - /// "Hide cursor" - /// - public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor"); - - /// - /// "Aim lines" - /// - public static LocalisableString AimLines => new TranslatableString(getKey(@"aim_lines"), @"Aim lines"); - /// /// "Seek backward {0} seconds" /// diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 4626698190df..fdf43c2f0961 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -17,6 +17,7 @@ using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Overlays.Settings; +using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -27,7 +28,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; using osu.Game.Users; @@ -410,6 +410,6 @@ public virtual IEnumerable CreateEditorSetupSections() => public virtual DifficultySection? CreateEditorDifficultySection() => null; - public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null; + public virtual AnalysisContainer? CreateAnalysisContainer(Replay replay, Playfield playfield) => null; } } diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs index 62d54374e724..69a71cf06ef5 100644 --- a/osu.Game/Rulesets/UI/AnalysisContainer.cs +++ b/osu.Game/Rulesets/UI/AnalysisContainer.cs @@ -3,16 +3,25 @@ using osu.Framework.Graphics.Containers; using osu.Game.Replays; +using osu.Game.Screens.Play.PlayerSettings; namespace osu.Game.Rulesets.UI { - public partial class AnalysisContainer : Container + public abstract partial class AnalysisContainer : Container { protected Replay Replay; + protected Playfield Playfield; - public AnalysisContainer(Replay replay) + public AnalysisSettings AnalysisSettings; + + public AnalysisContainer(Replay replay, Playfield playfield) { Replay = replay; + Playfield = playfield; + + AnalysisSettings = CreateAnalysisSettings(); } + + protected abstract AnalysisSettings CreateAnalysisSettings(); } } diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index 91531b28b407..e1f77cef1274 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -1,21 +1,16 @@ // 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.Replays; -using osu.Game.Rulesets.UI; +using osu.Game.Configuration; namespace osu.Game.Screens.Play.PlayerSettings { - public abstract partial class AnalysisSettings : PlayerSettingsGroup + public partial class AnalysisSettings : PlayerSettingsGroup { - protected DrawableRuleset DrawableRuleset; - - protected AnalysisSettings(DrawableRuleset drawableRuleset) + public AnalysisSettings() : base("Analysis Settings") { - DrawableRuleset = drawableRuleset; + AddRange(this.CreateSettingsControls()); } - - public abstract AnalysisContainer CreateAnalysisContainer(Replay replay); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 5d604003c8cb..af9568c08c89 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -72,12 +72,12 @@ private void load(OsuConfigManager config) HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset); + var analysisContainer = DrawableRuleset.Ruleset.CreateAnalysisContainer(GameplayState.Score.Replay, DrawableRuleset.Playfield); - if (analysisSettings != null) + if (analysisContainer != null) { - HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings); - DrawableRuleset.Playfield.AddAnalysisContainer(analysisSettings.CreateAnalysisContainer(GameplayState.Score.Replay)); + HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisContainer.AnalysisSettings); + DrawableRuleset.Playfield.AddAnalysisContainer(analysisContainer); } } From 56db29d0f5c7d798144660186ededd38a8ff03ae Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Tue, 3 Sep 2024 01:38:54 -0400 Subject: [PATCH 486/579] make test go indefinitely --- .../TestSceneHitMarker.cs | 91 ------------ .../TestSceneOsuAnalysisContainer.cs | 134 ++++++++++++++++++ .../UI/OsuAnalysisContainer.cs | 2 + 3 files changed, 136 insertions(+), 91 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs deleted file mode 100644 index e4c48f96b89d..000000000000 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs +++ /dev/null @@ -1,91 +0,0 @@ -// 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.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Replays; -using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Replays; -using osu.Game.Tests.Visual; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Tests -{ - public partial class TestSceneHitMarker : OsuTestScene - { - private TestOsuAnalysisContainer analysisContainer; - - [Test] - public void TestHitMarkers() - { - createAnalysisContainer(); - AddStep("enable hit markers", () => analysisContainer.HitMarkerEnabled.Value = true); - AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); - AddStep("disable hit markers", () => analysisContainer.HitMarkerEnabled.Value = false); - AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); - } - - [Test] - public void TestAimMarker() - { - createAnalysisContainer(); - AddStep("enable aim markers", () => analysisContainer.AimMarkersEnabled.Value = true); - AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); - AddStep("disable aim markers", () => analysisContainer.AimMarkersEnabled.Value = false); - AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); - } - - [Test] - public void TestAimLines() - { - createAnalysisContainer(); - AddStep("enable aim lines", () => analysisContainer.AimLinesEnabled.Value = true); - AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); - AddStep("disable aim lines", () => analysisContainer.AimLinesEnabled.Value = false); - AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); - } - - private void createAnalysisContainer() - { - AddStep("create new analysis container", () => Child = analysisContainer = new TestOsuAnalysisContainer(fabricateReplay())); - } - - private Replay fabricateReplay() - { - var frames = new List(); - - for (int i = 0; i < 50; i++) - { - frames.Add(new OsuReplayFrame - { - Time = Time.Current + i * 15, - Position = new Vector2(20 + i * 10, 20), - Actions = - { - i % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton - } - }); - } - - return new Replay { Frames = frames }; - } - - private partial class TestOsuAnalysisContainer : OsuAnalysisContainer - { - public TestOsuAnalysisContainer(Replay replay) - : base(replay) - { - } - - public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); - - public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); - - public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; - } - } -} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs new file mode 100644 index 000000000000..a17325655725 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -0,0 +1,134 @@ +// 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; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Threading; +using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneOsuAnalysisContainer : OsuTestScene + { + private TestOsuAnalysisContainer analysisContainer; + + [BackgroundDependencyLoader] + private void load() + { + Child = analysisContainer = createAnalysisContainer(); + } + + [Test] + public void TestHitMarkers() + { + var loop = createAnalysisContainer(); + AddStep("enable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = true); + AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); + AddStep("disable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = false); + AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); + } + + [Test] + public void TestAimMarker() + { + var loop = createAnalysisContainer(); + AddStep("enable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = true); + AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); + AddStep("disable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = false); + AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); + } + + [Test] + public void TestAimLines() + { + var loop = createAnalysisContainer(); + AddStep("enable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = true); + AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); + AddStep("disable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = false); + AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); + } + + private TestOsuAnalysisContainer createAnalysisContainer() => new TestOsuAnalysisContainer(); + + private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + { + public TestOsuAnalysisContainer() + : base(new Replay(), new OsuPlayfield()) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Replay = fabricateReplay(); + LoadReplay(); + + makeReplayLoop(); + } + + private void makeReplayLoop() + { + Scheduler.AddDelayed(() => + { + Replay = fabricateReplay(); + + HitMarkers.Clear(); + AimMarkers.Clear(); + AimLines.Clear(); + + LoadReplay(); + + makeReplayLoop(); + }, 15000); + } + + public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); + + public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); + + public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; + + private Replay fabricateReplay() + { + var frames = new List(); + var random = new Random(); + int posX = 250; + int posY = 250; + bool leftOrRight = false; + + for (int i = 0; i < 1000; i++) + { + posX = Math.Clamp(posX + random.Next(-10, 11), 0, 500); + posY = Math.Clamp(posY + random.Next(-10, 11), 0, 500); + + var actions = new List(); + + if (i % 20 == 0) + { + actions.Add(leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton); + leftOrRight = !leftOrRight; + } + + frames.Add(new OsuReplayFrame + { + Time = Time.Current + i * 15, + Position = new Vector2(posX, posY), + Actions = actions + }); + } + + return new Replay { Frames = frames }; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 3bddc479ef93..2f341a35bb0a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -142,6 +142,8 @@ protected override void Update() public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); + public void Clear() => lifetimeManager.ClearEntries(); + private void entryBecameAlive(LifetimeEntry entry) { aliveEntries.Add((AimPointEntry)entry); From a549cdd5b9f1b8a968e9a3e5d57c926db22b8675 Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Tue, 3 Sep 2024 04:49:50 -0400 Subject: [PATCH 487/579] persist analysis settings --- .../UI/OsuAnalysisContainer.cs | 27 ++++++++++++------- .../UI/OsuAnalysisSettings.cs | 10 +++++++ osu.Game/Configuration/OsuConfigManager.cs | 10 +++++++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 2f341a35bb0a..4eff1477727f 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -1,5 +1,4 @@ - -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -38,28 +37,38 @@ public OsuAnalysisContainer(Replay replay, Playfield playfield) HitMarkers = new HitMarkersContainer(), AimMarkers = new AimMarkersContainer { Depth = float.MinValue } }; + } protected override OsuAnalysisSettings CreateAnalysisSettings() { var settings = new OsuAnalysisSettings(); - settings.HitMarkersEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0); - settings.AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0); - settings.AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0); - settings.CursorHideEnabled.ValueChanged += e => Playfield.Cursor.FadeTo(e.NewValue ? 0 : 1); + settings.HitMarkersEnabled.ValueChanged += e => toggleHitMarkers(e.NewValue); + settings.AimMarkersEnabled.ValueChanged += e => toggleAimMarkers(e.NewValue); + settings.AimLinesEnabled.ValueChanged += e => toggleAimLines(e.NewValue); + settings.CursorHideEnabled.ValueChanged += e => toggleCursorHidden(e.NewValue); return settings; } [BackgroundDependencyLoader] private void load() { - HitMarkers.Hide(); - AimMarkers.Hide(); - AimLines.Hide(); + toggleHitMarkers(AnalysisSettings.HitMarkersEnabled.Value); + toggleAimMarkers(AnalysisSettings.AimMarkersEnabled.Value); + toggleAimLines(AnalysisSettings.AimLinesEnabled.Value); + toggleCursorHidden(AnalysisSettings.CursorHideEnabled.Value); LoadReplay(); } + private void toggleHitMarkers(bool value) => HitMarkers.FadeTo(value ? 1 : 0); + + private void toggleAimMarkers(bool value) => AimMarkers.FadeTo(value ? 1 : 0); + + private void toggleAimLines(bool value) => AimLines.FadeTo(value ? 1 : 0); + + private void toggleCursorHidden(bool value) => Playfield.Cursor.FadeTo(value ? 0 : 1); + protected void LoadReplay() { bool leftHeld = false; diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index c45b893a1c6c..ae81b2c0b8f4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Screens.Play.PlayerSettings; @@ -20,5 +21,14 @@ public partial class OsuAnalysisSettings : AnalysisSettings [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool CursorHideEnabled { get; } = new BindableBool(); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); + config.BindWith(OsuSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); + config.BindWith(OsuSetting.ReplayAimLinesEnabled, AimLinesEnabled); + config.BindWith(OsuSetting.ReplayCursorHideEnabled, CursorHideEnabled); + } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8d6c244b350c..8b75c9c934cd 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -154,6 +154,12 @@ protected override void InitialiseDefaults() SetDefault(OsuSetting.IncreaseFirstObjectVisibility, true); SetDefault(OsuSetting.GameplayDisableWinKey, true); + // Replay + SetDefault(OsuSetting.ReplayHitMarkersEnabled, false); + SetDefault(OsuSetting.ReplayAimMarkersEnabled, false); + SetDefault(OsuSetting.ReplayAimLinesEnabled, false); + SetDefault(OsuSetting.ReplayCursorHideEnabled, false); + // Update SetDefault(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -413,6 +419,10 @@ public enum OsuSetting EditorShowHitMarkers, EditorAutoSeekOnPlacement, DiscordRichPresence, + ReplayHitMarkersEnabled, + ReplayAimMarkersEnabled, + ReplayAimLinesEnabled, + ReplayCursorHideEnabled, ShowOnlineExplicitContent, LastProcessedMetadataId, From 6c89c4eed6f2e619b2932324c9173d0dcaf9f39d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2024 18:50:57 +0900 Subject: [PATCH 488/579] Fix rewind causing weirdness with progress bar animation --- osu.Game/Screens/Play/BreakOverlay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 7f9e879b442a..4ed8b69a77da 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -145,6 +145,13 @@ protected override void Update() base.Update(); remainingTimeBox.Height = Math.Min(8, remainingTimeBox.DrawWidth); + + // Keep things simple by resetting beat synced transforms on a rewind. + if (Clock.ElapsedFrameTime < 0) + { + remainingTimeBox.ClearTransforms(targetMember: nameof(Width)); + remainingTimeBox.Width = remainingTimeForCurrentPeriod; + } } protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) From 08224b416e9664f264366a4280edd891d1439782 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2024 19:11:34 +0900 Subject: [PATCH 489/579] Simplify update process by caching pending update info and early-handling edge case --- osu.Desktop/Updater/VelopackUpdateManager.cs | 28 +++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index 4d2535ed32c3..bf7122d7202c 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -25,11 +25,13 @@ public partial class VelopackUpdateManager : Game.Updater.UpdateManager [Resolved] private ILocalUserPlayInfo? localUserInfo { get; set; } + private UpdateInfo? pendingUpdate; + public VelopackUpdateManager() { updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", null, false), new UpdateOptions { - AllowVersionDowngrade = true + AllowVersionDowngrade = true, }); } @@ -52,33 +54,33 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific if (localUserInfo?.IsPlaying.Value == true) return false; - UpdateInfo? info = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false); - - // Handle no updates available. - if (info == null) + if (pendingUpdate != null) { - // If there's no updates pending restart, bail and retry later. - if (!updateManager.IsUpdatePendingRestart) return false; - // If there is an update pending restart, show the notification to restart again. notificationOverlay.Post(new UpdateApplicationCompleteNotification { Activated = () => { - restartToApplyUpdate(null); + restartToApplyUpdate(); return true; } }); return true; } + pendingUpdate = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false); + + // Handle no updates available. + if (pendingUpdate == null) + return false; + scheduleRecheck = false; if (notification == null) { notification = new UpdateProgressNotification { - CompletionClickAction = () => restartToApplyUpdate(info), + CompletionClickAction = restartToApplyUpdate, }; Schedule(() => notificationOverlay.Post(notification)); @@ -88,7 +90,7 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific try { - await updateManager.DownloadUpdatesAsync(info, p => notification.Progress = p / 100f).ConfigureAwait(false); + await updateManager.DownloadUpdatesAsync(pendingUpdate, p => notification.Progress = p / 100f).ConfigureAwait(false); notification.State = ProgressNotificationState.Completed; } @@ -117,9 +119,9 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific return true; } - private bool restartToApplyUpdate(UpdateInfo? info) + private bool restartToApplyUpdate() { - updateManager.WaitExitThenApplyUpdates(info?.TargetFullRelease); + updateManager.WaitExitThenApplyUpdates(pendingUpdate?.TargetFullRelease); Schedule(() => game.AttemptExit()); return true; } From b61023385a85a4508cd28b153d94fa233cc251a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2024 19:16:10 +0900 Subject: [PATCH 490/579] Don't log probable network failures to sentry --- osu.Desktop/Updater/VelopackUpdateManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index bf7122d7202c..5b4d281f8087 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -105,7 +105,7 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific { // we'll ignore this and retry later. can be triggered by no internet connection or thread abortion. scheduleRecheck = true; - Logger.Error(e, @"update check failed!"); + Logger.Log($@"update check failed ({e.Message})"); } finally { From 1f122ab38de24e819697fba9c1eb2c3b6b8f4ac1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2024 23:57:18 +0900 Subject: [PATCH 491/579] Apply new rider migration --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index a792b956dd41..2d0b7446f3e1 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -853,6 +853,7 @@ See the LICENCE file in the repository root for full licence text. True True True + True True True True From 421f245c3102bb2ab3719b7ff4717b401a0db11a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 14:31:59 +0900 Subject: [PATCH 492/579] Fix per-frame allocations in `BeatmapCarousel` --- .../SongSelect/TestSceneBeatmapCarousel.cs | 4 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 11 +++++++---- .../Carousel/DrawableCarouselBeatmapSet.cs | 18 ++++++++---------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index ec072a3dd2d5..218a67e81845 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1405,9 +1405,9 @@ public IEnumerable Items yield return item; - if (item is DrawableCarouselBeatmapSet set) + if (item is DrawableCarouselBeatmapSet set && set.Beatmaps?.IsLoaded == true) { - foreach (var difficulty in set.DrawableBeatmaps) + foreach (var difficulty in set.Beatmaps) yield return difficulty; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 87cea45e874b..f7e0eae4a55c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -857,8 +857,9 @@ protected override void Update() // Add those items within the previously found index range that should be displayed. foreach (var item in toDisplay) { - var panel = setPool.Get(p => p.Item = item); + var panel = setPool.Get(); + panel.Item = item; panel.Y = item.CarouselYPosition; Scroll.Add(panel); @@ -898,10 +899,12 @@ protected override void Update() Scroll.ChangeChildDepth(item, hasPassedSelection ? -item.Item.CarouselYPosition : item.Item.CarouselYPosition); } - if (item is DrawableCarouselBeatmapSet set) + if (item is DrawableCarouselBeatmapSet set && set.Beatmaps?.IsLoaded == true) { - foreach (var diff in set.DrawableBeatmaps) + foreach (var diff in set.Beatmaps) + { updateItem(diff, item); + } } } } @@ -1101,7 +1104,7 @@ private static float offsetX(float dist, float halfHeight) } /// - /// Update a item's x position and multiplicative alpha based on its y position and + /// Update an item's x position and multiplicative alpha based on its y position and /// the current scroll position. /// /// The item to be updated. diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 1cd8b065fc8d..9a01b46216ff 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -51,9 +51,7 @@ public partial class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasCont [Resolved] private IBindable ruleset { get; set; } = null!; - public IEnumerable DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty() : beatmapContainer.AliveChildren; - - private Container? beatmapContainer; + public Container? Beatmaps; private BeatmapSetInfo beatmapSet = null!; @@ -126,7 +124,7 @@ protected override void UpdateItem() Content.Clear(); Header.Clear(); - beatmapContainer = null; + Beatmaps = null; beatmapsLoadTask = null; if (Item == null) @@ -164,7 +162,7 @@ private void updateBeatmapDifficulties() // if we are already displaying all the correct beatmaps, only run animation updates. // note that the displayed beatmaps may change due to the applied filter. // a future optimisation could add/remove only changed difficulties rather than reinitialise. - if (beatmapContainer != null && visibleBeatmaps.Length == beatmapContainer.Count && visibleBeatmaps.All(b => beatmapContainer.Any(c => c.Item == b))) + if (Beatmaps != null && visibleBeatmaps.Length == Beatmaps.Count && visibleBeatmaps.All(b => Beatmaps.Any(c => c.Item == b))) { updateBeatmapYPositions(); } @@ -173,17 +171,17 @@ private void updateBeatmapDifficulties() // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. // can be improved in the future. - beatmapContainer = new Container + Beatmaps = new Container { X = 100, RelativeSizeAxes = Axes.Both, ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()!) }; - beatmapsLoadTask = LoadComponentAsync(beatmapContainer, loaded => + beatmapsLoadTask = LoadComponentAsync(Beatmaps, loaded => { // make sure the pooled target hasn't changed. - if (beatmapContainer != loaded) + if (Beatmaps != loaded) return; Content.Child = loaded; @@ -244,7 +242,7 @@ private void loadContentIfRequired() private void updateBeatmapYPositions() { - if (beatmapContainer == null) + if (Beatmaps == null) return; if (beatmapsLoadTask == null || !beatmapsLoadTask.IsCompleted) @@ -254,7 +252,7 @@ private void updateBeatmapYPositions() bool isSelected = Item?.State.Value == CarouselItemState.Selected; - foreach (var panel in beatmapContainer) + foreach (var panel in Beatmaps) { Debug.Assert(panel.Item != null); From 97a51af5a06351d64944951963d82dba4df65c16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 14:52:52 +0900 Subject: [PATCH 493/579] Fix one more unnecessary enumerator allocation --- .../Overlays/NotificationOverlayToastTray.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index d2899f29b8fa..df07b4f13825 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -153,8 +153,22 @@ protected override void Update() { base.Update(); - float height = toastFlow.Count > 0 ? toastFlow.DrawHeight + 120 : 0; - float alpha = toastFlow.Count > 0 ? MathHelper.Clamp(toastFlow.DrawHeight / 41, 0, 1) * toastFlow.Children.Max(n => n.Alpha) : 0; + float height = 0; + float alpha = 0; + + if (toastFlow.Count > 0) + { + float maxNotificationAlpha = 0; + + foreach (var t in toastFlow) + { + if (t.Alpha > maxNotificationAlpha) + maxNotificationAlpha = t.Alpha; + } + + height = toastFlow.DrawHeight + 120; + alpha = MathHelper.Clamp(toastFlow.DrawHeight / 41, 0, 1) * maxNotificationAlpha; + } toastContentBackground.Height = (float)Interpolation.DampContinuously(toastContentBackground.Height, height, 10, Clock.ElapsedFrameTime); toastContentBackground.Alpha = (float)Interpolation.DampContinuously(toastContentBackground.Alpha, alpha, 10, Clock.ElapsedFrameTime); From dfe11dc68a4f381a3d6ec78d30462929dd3efe5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 15:25:36 +0900 Subject: [PATCH 494/579] Use `for` with exposed `IReadOnlyList` rather than making internal container public --- .../SongSelect/TestSceneBeatmapCarousel.cs | 4 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 +++----- .../Carousel/DrawableCarouselBeatmapSet.cs | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 218a67e81845..ec072a3dd2d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1405,9 +1405,9 @@ public IEnumerable Items yield return item; - if (item is DrawableCarouselBeatmapSet set && set.Beatmaps?.IsLoaded == true) + if (item is DrawableCarouselBeatmapSet set) { - foreach (var difficulty in set.Beatmaps) + foreach (var difficulty in set.DrawableBeatmaps) yield return difficulty; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f7e0eae4a55c..a6a6a2f58528 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -899,12 +899,10 @@ protected override void Update() Scroll.ChangeChildDepth(item, hasPassedSelection ? -item.Item.CarouselYPosition : item.Item.CarouselYPosition); } - if (item is DrawableCarouselBeatmapSet set && set.Beatmaps?.IsLoaded == true) + if (item is DrawableCarouselBeatmapSet set) { - foreach (var diff in set.Beatmaps) - { - updateItem(diff, item); - } + for (int i = 0; i < set.DrawableBeatmaps.Count; i++) + updateItem(set.DrawableBeatmaps[i], item); } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 9a01b46216ff..eba40994e22e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -51,7 +51,9 @@ public partial class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasCont [Resolved] private IBindable ruleset { get; set; } = null!; - public Container? Beatmaps; + public IReadOnlyList DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Array.Empty() : beatmapContainer; + + private Container? beatmapContainer; private BeatmapSetInfo beatmapSet = null!; @@ -124,7 +126,7 @@ protected override void UpdateItem() Content.Clear(); Header.Clear(); - Beatmaps = null; + beatmapContainer = null; beatmapsLoadTask = null; if (Item == null) @@ -162,7 +164,7 @@ private void updateBeatmapDifficulties() // if we are already displaying all the correct beatmaps, only run animation updates. // note that the displayed beatmaps may change due to the applied filter. // a future optimisation could add/remove only changed difficulties rather than reinitialise. - if (Beatmaps != null && visibleBeatmaps.Length == Beatmaps.Count && visibleBeatmaps.All(b => Beatmaps.Any(c => c.Item == b))) + if (beatmapContainer != null && visibleBeatmaps.Length == beatmapContainer.Count && visibleBeatmaps.All(b => beatmapContainer.Any(c => c.Item == b))) { updateBeatmapYPositions(); } @@ -171,17 +173,17 @@ private void updateBeatmapDifficulties() // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. // can be improved in the future. - Beatmaps = new Container + beatmapContainer = new Container { X = 100, RelativeSizeAxes = Axes.Both, ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()!) }; - beatmapsLoadTask = LoadComponentAsync(Beatmaps, loaded => + beatmapsLoadTask = LoadComponentAsync(beatmapContainer, loaded => { // make sure the pooled target hasn't changed. - if (Beatmaps != loaded) + if (beatmapContainer != loaded) return; Content.Child = loaded; @@ -242,7 +244,7 @@ private void loadContentIfRequired() private void updateBeatmapYPositions() { - if (Beatmaps == null) + if (beatmapContainer == null) return; if (beatmapsLoadTask == null || !beatmapsLoadTask.IsCompleted) @@ -252,7 +254,7 @@ private void updateBeatmapYPositions() bool isSelected = Item?.State.Value == CarouselItemState.Selected; - foreach (var panel in Beatmaps) + foreach (var panel in beatmapContainer) { Debug.Assert(panel.Item != null); From e564e8c04895336559a16b093cbc905de8162b9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 16:08:18 +0900 Subject: [PATCH 495/579] Add todo about fixing stutter on update application --- osu.Desktop/Updater/VelopackUpdateManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index 5b4d281f8087..527892413a3f 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -121,6 +121,8 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific private bool restartToApplyUpdate() { + // TODO: Migrate this to async flow whenever available (see https://github.com/ppy/osu/pull/28743#discussion_r1740505665). + // Currently there's an internal Thread.Sleep(300) which will cause a stutter when the user clicks to restart. updateManager.WaitExitThenApplyUpdates(pendingUpdate?.TargetFullRelease); Schedule(() => game.AttemptExit()); return true; From c89597b060cae084899db82c3b6c5040a17f794a Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Wed, 4 Sep 2024 03:37:52 -0400 Subject: [PATCH 496/579] fix config mistake --- .../Configuration/OsuRulesetConfigManager.cs | 11 +++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 ++ .../UI/OsuAnalysisContainer.cs | 13 ++++++------- osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 17 ++++++++++++----- osu.Game/Configuration/OsuConfigManager.cs | 10 ---------- osu.Game/Rulesets/Ruleset.cs | 3 --- osu.Game/Rulesets/UI/AnalysisContainer.cs | 10 +++++----- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 ++ .../Play/PlayerSettings/AnalysisSettings.cs | 7 ++++++- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- 11 files changed, 45 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index 2056a50edab2..23b7b9c1faa2 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -24,6 +24,11 @@ protected override void InitialiseDefaults() SetDefault(OsuRulesetSetting.ShowCursorTrail, true); SetDefault(OsuRulesetSetting.ShowCursorRipples, false); SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); + + SetDefault(OsuRulesetSetting.ReplayHitMarkersEnabled, false); + SetDefault(OsuRulesetSetting.ReplayAimMarkersEnabled, false); + SetDefault(OsuRulesetSetting.ReplayAimLinesEnabled, false); + SetDefault(OsuRulesetSetting.ReplayCursorHideEnabled, false); } } @@ -34,5 +39,11 @@ public enum OsuRulesetSetting ShowCursorTrail, ShowCursorRipples, PlayfieldBorderStyle, + + // Replay + ReplayHitMarkersEnabled, + ReplayAimMarkersEnabled, + ReplayAimLinesEnabled, + ReplayCursorHideEnabled, } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index a8a1d98bf3ad..be48ef9acc47 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -13,7 +13,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays.Settings; -using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -361,8 +360,6 @@ public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDiffi return adjustedDifficulty; } - public override OsuAnalysisContainer CreateAnalysisContainer(Replay replay, Playfield playfield) => new OsuAnalysisContainer(replay, playfield); - public override bool EditorShowScrollSpeed => false; } } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index f0390ad716d5..3c6456957b92 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -68,5 +68,7 @@ public override double GameplayStartTime return 0; } } + + public override AnalysisContainer CreateAnalysisContainer(Replay replay) => new OsuAnalysisContainer(replay, this); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 4eff1477727f..57401edece29 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -22,14 +22,14 @@ public partial class OsuAnalysisContainer : AnalysisContainer { public new OsuAnalysisSettings AnalysisSettings => (OsuAnalysisSettings)base.AnalysisSettings; - protected new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; + protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset; protected HitMarkersContainer HitMarkers; protected AimMarkersContainer AimMarkers; protected AimLinesContainer AimLines; - public OsuAnalysisContainer(Replay replay, Playfield playfield) - : base(replay, playfield) + public OsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + : base(replay, drawableRuleset) { InternalChildren = new Drawable[] { @@ -37,12 +37,11 @@ public OsuAnalysisContainer(Replay replay, Playfield playfield) HitMarkers = new HitMarkersContainer(), AimMarkers = new AimMarkersContainer { Depth = float.MinValue } }; - } - protected override OsuAnalysisSettings CreateAnalysisSettings() + protected override OsuAnalysisSettings CreateAnalysisSettings(Ruleset ruleset) { - var settings = new OsuAnalysisSettings(); + var settings = new OsuAnalysisSettings((OsuRuleset)ruleset); settings.HitMarkersEnabled.ValueChanged += e => toggleHitMarkers(e.NewValue); settings.AimMarkersEnabled.ValueChanged += e => toggleAimMarkers(e.NewValue); settings.AimLinesEnabled.ValueChanged += e => toggleAimLines(e.NewValue); @@ -67,7 +66,7 @@ private void load() private void toggleAimLines(bool value) => AimLines.FadeTo(value ? 1 : 0); - private void toggleCursorHidden(bool value) => Playfield.Cursor.FadeTo(value ? 0 : 1); + private void toggleCursorHidden(bool value) => DrawableRuleset.Playfield.Cursor.FadeTo(value ? 0 : 1); protected void LoadReplay() { diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index ae81b2c0b8f4..6e11c87c3a8f 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -4,12 +4,18 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Screens.Play.PlayerSettings; namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { + public OsuAnalysisSettings(Ruleset ruleset) + : base(ruleset) + { + } + [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HitMarkersEnabled { get; } = new BindableBool(); @@ -23,12 +29,13 @@ public partial class OsuAnalysisSettings : AnalysisSettings public BindableBool CursorHideEnabled { get; } = new BindableBool(); [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(IRulesetConfigCache cache) { - config.BindWith(OsuSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); - config.BindWith(OsuSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); - config.BindWith(OsuSetting.ReplayAimLinesEnabled, AimLinesEnabled); - config.BindWith(OsuSetting.ReplayCursorHideEnabled, CursorHideEnabled); + var config = (OsuRulesetConfigManager)cache.GetConfigFor(Ruleset)!; + config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); + config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); + config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, AimLinesEnabled); + config.BindWith(OsuRulesetSetting.ReplayCursorHideEnabled, CursorHideEnabled); } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8b75c9c934cd..8d6c244b350c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -154,12 +154,6 @@ protected override void InitialiseDefaults() SetDefault(OsuSetting.IncreaseFirstObjectVisibility, true); SetDefault(OsuSetting.GameplayDisableWinKey, true); - // Replay - SetDefault(OsuSetting.ReplayHitMarkersEnabled, false); - SetDefault(OsuSetting.ReplayAimMarkersEnabled, false); - SetDefault(OsuSetting.ReplayAimLinesEnabled, false); - SetDefault(OsuSetting.ReplayCursorHideEnabled, false); - // Update SetDefault(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -419,10 +413,6 @@ public enum OsuSetting EditorShowHitMarkers, EditorAutoSeekOnPlacement, DiscordRichPresence, - ReplayHitMarkersEnabled, - ReplayAimMarkersEnabled, - ReplayAimLinesEnabled, - ReplayCursorHideEnabled, ShowOnlineExplicitContent, LastProcessedMetadataId, diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index fdf43c2f0961..2e48b8e16fcc 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -17,7 +17,6 @@ using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Overlays.Settings; -using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -409,7 +408,5 @@ public virtual IEnumerable CreateEditorSetupSections() => public virtual bool EditorShowScrollSpeed => true; public virtual DifficultySection? CreateEditorDifficultySection() => null; - - public virtual AnalysisContainer? CreateAnalysisContainer(Replay replay, Playfield playfield) => null; } } diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs index 69a71cf06ef5..b6c2a8c1c805 100644 --- a/osu.Game/Rulesets/UI/AnalysisContainer.cs +++ b/osu.Game/Rulesets/UI/AnalysisContainer.cs @@ -10,18 +10,18 @@ namespace osu.Game.Rulesets.UI public abstract partial class AnalysisContainer : Container { protected Replay Replay; - protected Playfield Playfield; + protected DrawableRuleset DrawableRuleset; public AnalysisSettings AnalysisSettings; - public AnalysisContainer(Replay replay, Playfield playfield) + protected AnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) { Replay = replay; - Playfield = playfield; + DrawableRuleset = drawableRuleset; - AnalysisSettings = CreateAnalysisSettings(); + AnalysisSettings = CreateAnalysisSettings(drawableRuleset.Ruleset); } - protected abstract AnalysisSettings CreateAnalysisSettings(); + protected abstract AnalysisSettings CreateAnalysisSettings(Ruleset ruleset); } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a28b2716cb7b..5b1f59d549ad 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -596,6 +596,8 @@ public HitWindows FirstAvailableHitWindows /// Invoked when the user requests to pause while the resume overlay is active. /// public abstract void CancelResume(); + + public virtual AnalysisContainer CreateAnalysisContainer(Replay replay) => null; } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs index e1f77cef1274..4c64eef92f9d 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs @@ -2,14 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Configuration; +using osu.Game.Rulesets; namespace osu.Game.Screens.Play.PlayerSettings { public partial class AnalysisSettings : PlayerSettingsGroup { - public AnalysisSettings() + protected Ruleset Ruleset; + + public AnalysisSettings(Ruleset ruleset) : base("Analysis Settings") { + Ruleset = ruleset; + AddRange(this.CreateSettingsControls()); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index af9568c08c89..82a2f0925040 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -72,7 +72,7 @@ private void load(OsuConfigManager config) HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - var analysisContainer = DrawableRuleset.Ruleset.CreateAnalysisContainer(GameplayState.Score.Replay, DrawableRuleset.Playfield); + var analysisContainer = DrawableRuleset.CreateAnalysisContainer(GameplayState.Score.Replay); if (analysisContainer != null) { From 59ff8c498417f97760b83ccf55fdfb65fd454428 Mon Sep 17 00:00:00 2001 From: Sheppsu Date: Wed, 4 Sep 2024 03:38:13 -0400 Subject: [PATCH 497/579] fix analysis container creation --- .../TestSceneOsuAnalysisContainer.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index a17325655725..0fc91513e62b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -8,11 +8,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Threading; using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; using osuTK; @@ -31,7 +32,6 @@ private void load() [Test] public void TestHitMarkers() { - var loop = createAnalysisContainer(); AddStep("enable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = true); AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); AddStep("disable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = false); @@ -41,7 +41,6 @@ public void TestHitMarkers() [Test] public void TestAimMarker() { - var loop = createAnalysisContainer(); AddStep("enable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = true); AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); AddStep("disable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = false); @@ -51,19 +50,28 @@ public void TestAimMarker() [Test] public void TestAimLines() { - var loop = createAnalysisContainer(); AddStep("enable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = true); AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); AddStep("disable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = false); AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); } - private TestOsuAnalysisContainer createAnalysisContainer() => new TestOsuAnalysisContainer(); + private TestOsuAnalysisContainer createAnalysisContainer() + { + var replay = new Replay(); + var ruleset = new OsuRuleset(); + var beatmap = new OsuBeatmap(); + var drawableRuleset = new DrawableOsuRuleset(ruleset, beatmap); + // Load playfield cursor to avoid errors + Add(drawableRuleset); + + return new TestOsuAnalysisContainer(replay, drawableRuleset); + } private partial class TestOsuAnalysisContainer : OsuAnalysisContainer { - public TestOsuAnalysisContainer() - : base(new Replay(), new OsuPlayfield()) + public TestOsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + : base(replay, drawableRuleset) { } From 86309f4b4605b7ac82853006acf08bec8cb2a84f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 17:25:36 +0900 Subject: [PATCH 498/579] Revert "Woopsie! I accidentally added one too many semi-colons, so I moved it here into the commit instead ;" This reverts commit 582ffcfc9722342f73d83778fc950cacc1bb9439. Revert "Mod customisation header's color is now based on the state of the panel rather than the hover of the container." This reverts commit e3457d850157808928b7ef912b8ecc562826f5a2. --- .../Overlays/Mods/ModCustomisationHeader.cs | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index da77d8dac8cb..32fd5a37aa54 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Mods { - public partial class ModCustomisationHeader : OsuClickableContainer + public partial class ModCustomisationHeader : OsuHoverContainer { private Box background = null!; private Box backgroundFlash = null!; @@ -28,6 +29,8 @@ public partial class ModCustomisationHeader : OsuClickableContainer [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + protected override IEnumerable EffectTargets => new[] { background }; + public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); private readonly ModCustomisationPanel panel; @@ -49,7 +52,6 @@ private void load() background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Dark3, }, backgroundFlash = new Box { @@ -82,6 +84,9 @@ private void load() } } }; + + IdleColour = colourProvider.Dark3; + HoverColour = colourProvider.Light4; } protected override void LoadComplete() @@ -105,20 +110,6 @@ protected override void LoadComplete() { icon.ScaleTo(v.NewValue > ModCustomisationPanelState.Collapsed ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); }, true); - - panel.ExpandedState.BindValueChanged(v => - { - switch (v.NewValue) - { - case ModCustomisationPanelState.Expanded: - case ModCustomisationPanelState.ExpandedByMod: - fadeBackgroundColor(colourProvider.Light4); - break; - default: - fadeBackgroundColor(colourProvider.Dark3); - break; - } - }, false); } protected override bool OnHover(HoverEvent e) @@ -128,10 +119,5 @@ protected override bool OnHover(HoverEvent e) return base.OnHover(e); } - - private void fadeBackgroundColor(Color4 color) - { - background.FadeColour(color, 500, Easing.OutQuint); - } } } From 7cd24ba58ecedd2971e4f06b637e1dab4bdeb00d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:00:07 +0900 Subject: [PATCH 499/579] Disallow mistimed firing of beat sync for break overlay for now It doesn't work well with pause/resume. --- osu.Game/Screens/Play/BreakOverlay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 4ed8b69a77da..1fdb9402bce8 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -53,6 +53,10 @@ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) MinimumBeatLength = 200; + // Doesn't play well with pause/unpause. + // This might mean that some beats don't animate if the user is running <60fps, but we'll deal with that if anyone notices. + AllowMistimedEventFiring = false; + Child = fadeContainer = new Container { Alpha = 0, From 045096b08ac771c649809546a53290d01d46857b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:00:48 +0900 Subject: [PATCH 500/579] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2609fd42c36e..5f3dd2f6f4ab 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 1056f4b44174..9d9b42a16378 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From cb9d1d49a225cbf58c253ea87ae600f9cb0716c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:01:06 +0900 Subject: [PATCH 501/579] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6952de2fa59a..b2bf8c7eb911 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From a417fec2347a01bb4e2ab1be308c474f4a240ab3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:35:27 +0900 Subject: [PATCH 502/579] Move analysis container implementation completely local to osu! ruleset --- .../TestSceneOsuAnalysisContainer.cs | 129 +++++++----------- .../UI/DrawableOsuRuleset.cs | 10 +- .../UI/OsuAnalysisContainer.cs | 51 +++---- .../UI/OsuAnalysisSettings.cs | 17 ++- osu.Game/Rulesets/Ruleset.cs | 2 - osu.Game/Rulesets/UI/AnalysisContainer.cs | 27 ---- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 - osu.Game/Rulesets/UI/Playfield.cs | 6 - .../Play/PlayerSettings/AnalysisSettings.cs | 21 --- osu.Game/Screens/Play/ReplayPlayer.cs | 8 -- 10 files changed, 93 insertions(+), 180 deletions(-) delete mode 100644 osu.Game/Rulesets/UI/AnalysisContainer.cs delete mode 100644 osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 0fc91513e62b..536de8b41ac0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.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.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Replays; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Replays; @@ -21,122 +20,92 @@ namespace osu.Game.Rulesets.Osu.Tests { public partial class TestSceneOsuAnalysisContainer : OsuTestScene { - private TestOsuAnalysisContainer analysisContainer; + private TestOsuAnalysisContainer analysisContainer = null!; - [BackgroundDependencyLoader] - private void load() + [SetUpSteps] + public void SetUpSteps() { - Child = analysisContainer = createAnalysisContainer(); + AddStep("create analysis container", () => + { + DrawableOsuRuleset drawableRuleset = new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()); + + Children = new Drawable[] + { + drawableRuleset, + analysisContainer = new TestOsuAnalysisContainer(fabricateReplay(), drawableRuleset), + }; + }); } [Test] public void TestHitMarkers() { - AddStep("enable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = true); + AddStep("enable hit markers", () => analysisContainer.Settings.HitMarkersEnabled.Value = true); AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); - AddStep("disable hit markers", () => analysisContainer.AnalysisSettings.HitMarkersEnabled.Value = false); + AddStep("disable hit markers", () => analysisContainer.Settings.HitMarkersEnabled.Value = false); AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); } [Test] public void TestAimMarker() { - AddStep("enable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = true); + AddStep("enable aim markers", () => analysisContainer.Settings.AimMarkersEnabled.Value = true); AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); - AddStep("disable aim markers", () => analysisContainer.AnalysisSettings.AimMarkersEnabled.Value = false); + AddStep("disable aim markers", () => analysisContainer.Settings.AimMarkersEnabled.Value = false); AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); } [Test] public void TestAimLines() { - AddStep("enable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = true); + AddStep("enable aim lines", () => analysisContainer.Settings.AimLinesEnabled.Value = true); AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); - AddStep("disable aim lines", () => analysisContainer.AnalysisSettings.AimLinesEnabled.Value = false); + AddStep("disable aim lines", () => analysisContainer.Settings.AimLinesEnabled.Value = false); AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); } - private TestOsuAnalysisContainer createAnalysisContainer() - { - var replay = new Replay(); - var ruleset = new OsuRuleset(); - var beatmap = new OsuBeatmap(); - var drawableRuleset = new DrawableOsuRuleset(ruleset, beatmap); - // Load playfield cursor to avoid errors - Add(drawableRuleset); - - return new TestOsuAnalysisContainer(replay, drawableRuleset); - } - - private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + private Replay fabricateReplay() { - public TestOsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) - : base(replay, drawableRuleset) - { - } + var frames = new List(); + var random = new Random(); + int posX = 250; + int posY = 250; + bool leftOrRight = false; - [BackgroundDependencyLoader] - private void load() + for (int i = 0; i < 1000; i++) { - Replay = fabricateReplay(); - LoadReplay(); + posX = Math.Clamp(posX + random.Next(-10, 11), 0, 500); + posY = Math.Clamp(posY + random.Next(-10, 11), 0, 500); - makeReplayLoop(); - } + var actions = new List(); - private void makeReplayLoop() - { - Scheduler.AddDelayed(() => + if (i % 20 == 0) { - Replay = fabricateReplay(); + actions.Add(leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton); + leftOrRight = !leftOrRight; + } - HitMarkers.Clear(); - AimMarkers.Clear(); - AimLines.Clear(); + frames.Add(new OsuReplayFrame + { + Time = Time.Current + i * 15, + Position = new Vector2(posX, posY), + Actions = actions + }); + } - LoadReplay(); + return new Replay { Frames = frames }; + } - makeReplayLoop(); - }, 15000); + private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + { + public TestOsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + : base(replay, drawableRuleset) + { } public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); - public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); - public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; - - private Replay fabricateReplay() - { - var frames = new List(); - var random = new Random(); - int posX = 250; - int posY = 250; - bool leftOrRight = false; - - for (int i = 0; i < 1000; i++) - { - posX = Math.Clamp(posX + random.Next(-10, 11), 0, 500); - posY = Math.Clamp(posY + random.Next(-10, 11), 0, 500); - - var actions = new List(); - - if (i % 20 == 0) - { - actions.Add(leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton); - leftOrRight = !leftOrRight; - } - - frames.Add(new OsuReplayFrame - { - Time = Time.Current + i * 15, - Position = new Vector2(posX, posY), - Actions = actions - }); - } - - return new Replay { Frames = frames }; - } } } } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 3c6456957b92..ba0768db5dea 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -36,6 +36,14 @@ public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList { } + protected override void LoadComplete() + { + if (HasReplayLoaded.Value) + LoadComponentAsync(new OsuAnalysisContainer(ReplayScore.Replay, this), PlayfieldAdjustmentContainer.Add); + + base.LoadComplete(); + } + public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) => null; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor @@ -68,7 +76,5 @@ public override double GameplayStartTime return 0; } } - - public override AnalysisContainer CreateAnalysisContainer(Replay replay) => new OsuAnalysisContainer(replay, this); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs index 57401edece29..19e53944c964 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; @@ -18,62 +19,62 @@ namespace osu.Game.Rulesets.Osu.UI { - public partial class OsuAnalysisContainer : AnalysisContainer + public partial class OsuAnalysisContainer : CompositeDrawable { - public new OsuAnalysisSettings AnalysisSettings => (OsuAnalysisSettings)base.AnalysisSettings; + protected readonly HitMarkersContainer HitMarkers; + protected readonly AimMarkersContainer AimMarkers; + protected readonly AimLinesContainer AimLines; - protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset; + public OsuAnalysisSettings Settings = null!; - protected HitMarkersContainer HitMarkers; - protected AimMarkersContainer AimMarkers; - protected AimLinesContainer AimLines; + private readonly Replay replay; + private readonly DrawableRuleset drawableRuleset; public OsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) - : base(replay, drawableRuleset) { + this.replay = replay; + this.drawableRuleset = drawableRuleset; + InternalChildren = new Drawable[] { - AimLines = new AimLinesContainer { Depth = float.MaxValue }, HitMarkers = new HitMarkersContainer(), - AimMarkers = new AimMarkersContainer { Depth = float.MinValue } + AimLines = new AimLinesContainer(), + AimMarkers = new AimMarkersContainer(), }; } - protected override OsuAnalysisSettings CreateAnalysisSettings(Ruleset ruleset) - { - var settings = new OsuAnalysisSettings((OsuRuleset)ruleset); - settings.HitMarkersEnabled.ValueChanged += e => toggleHitMarkers(e.NewValue); - settings.AimMarkersEnabled.ValueChanged += e => toggleAimMarkers(e.NewValue); - settings.AimLinesEnabled.ValueChanged += e => toggleAimLines(e.NewValue); - settings.CursorHideEnabled.ValueChanged += e => toggleCursorHidden(e.NewValue); - return settings; - } - [BackgroundDependencyLoader] private void load() { - toggleHitMarkers(AnalysisSettings.HitMarkersEnabled.Value); - toggleAimMarkers(AnalysisSettings.AimMarkersEnabled.Value); - toggleAimLines(AnalysisSettings.AimLinesEnabled.Value); - toggleCursorHidden(AnalysisSettings.CursorHideEnabled.Value); + AddInternal(Settings = new OsuAnalysisSettings()); LoadReplay(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Settings.HitMarkersEnabled.BindValueChanged(e => toggleHitMarkers(e.NewValue), true); + Settings.AimMarkersEnabled.BindValueChanged(e => toggleAimMarkers(e.NewValue), true); + Settings.AimLinesEnabled.BindValueChanged(e => toggleAimLines(e.NewValue), true); + Settings.CursorHideEnabled.BindValueChanged(e => toggleCursorHidden(e.NewValue), true); + } + private void toggleHitMarkers(bool value) => HitMarkers.FadeTo(value ? 1 : 0); private void toggleAimMarkers(bool value) => AimMarkers.FadeTo(value ? 1 : 0); private void toggleAimLines(bool value) => AimLines.FadeTo(value ? 1 : 0); - private void toggleCursorHidden(bool value) => DrawableRuleset.Playfield.Cursor.FadeTo(value ? 0 : 1); + private void toggleCursorHidden(bool value) => drawableRuleset.Playfield.Cursor.FadeTo(value ? 0 : 1); protected void LoadReplay() { bool leftHeld = false; bool rightHeld = false; - foreach (var frame in Replay.Frames) + foreach (var frame in replay.Frames) { var osuFrame = (OsuReplayFrame)frame; diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 6e11c87c3a8f..5419d4e17ae7 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -9,13 +9,8 @@ namespace osu.Game.Rulesets.Osu.UI { - public partial class OsuAnalysisSettings : AnalysisSettings + public partial class OsuAnalysisSettings : PlayerSettingsGroup { - public OsuAnalysisSettings(Ruleset ruleset) - : base(ruleset) - { - } - [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HitMarkersEnabled { get; } = new BindableBool(); @@ -28,10 +23,18 @@ public OsuAnalysisSettings(Ruleset ruleset) [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool CursorHideEnabled { get; } = new BindableBool(); + public OsuAnalysisSettings() + : base("Analysis Settings") + { + } + [BackgroundDependencyLoader] private void load(IRulesetConfigCache cache) { - var config = (OsuRulesetConfigManager)cache.GetConfigFor(Ruleset)!; + AddRange(this.CreateSettingsControls()); + + var config = (OsuRulesetConfigManager)cache.GetConfigFor(new OsuRuleset())!; + config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, AimLinesEnabled); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 2e48b8e16fcc..5af1fd386c3a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -406,7 +406,5 @@ public virtual IEnumerable CreateEditorSetupSections() => /// Can be overridden to avoid showing scroll speed changes in the editor. /// public virtual bool EditorShowScrollSpeed => true; - - public virtual DifficultySection? CreateEditorDifficultySection() => null; } } diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs deleted file mode 100644 index b6c2a8c1c805..000000000000 --- a/osu.Game/Rulesets/UI/AnalysisContainer.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Containers; -using osu.Game.Replays; -using osu.Game.Screens.Play.PlayerSettings; - -namespace osu.Game.Rulesets.UI -{ - public abstract partial class AnalysisContainer : Container - { - protected Replay Replay; - protected DrawableRuleset DrawableRuleset; - - public AnalysisSettings AnalysisSettings; - - protected AnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) - { - Replay = replay; - DrawableRuleset = drawableRuleset; - - AnalysisSettings = CreateAnalysisSettings(drawableRuleset.Ruleset); - } - - protected abstract AnalysisSettings CreateAnalysisSettings(Ruleset ruleset); - } -} diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5b1f59d549ad..a28b2716cb7b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -596,8 +596,6 @@ public HitWindows FirstAvailableHitWindows /// Invoked when the user requests to pause while the resume overlay is active. /// public abstract void CancelResume(); - - public virtual AnalysisContainer CreateAnalysisContainer(Replay replay) => null; } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index e116acdc1915..90a2f63faa8a 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -291,12 +291,6 @@ protected override void Update() /// protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); - /// - /// Adds an analysis container to internal children for replays. - /// - /// - public virtual void AddAnalysisContainer(AnalysisContainer analysisContainer) => AddInternal(analysisContainer); - #region Pooling support private readonly Dictionary pools = new Dictionary(); diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs deleted file mode 100644 index 4c64eef92f9d..000000000000 --- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs +++ /dev/null @@ -1,21 +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.Configuration; -using osu.Game.Rulesets; - -namespace osu.Game.Screens.Play.PlayerSettings -{ - public partial class AnalysisSettings : PlayerSettingsGroup - { - protected Ruleset Ruleset; - - public AnalysisSettings(Ruleset ruleset) - : base("Analysis Settings") - { - Ruleset = ruleset; - - AddRange(this.CreateSettingsControls()); - } - } -} diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 82a2f0925040..ff60dbc0d0d1 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -71,14 +71,6 @@ private void load(OsuConfigManager config) playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate); HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); - - var analysisContainer = DrawableRuleset.CreateAnalysisContainer(GameplayState.Score.Replay); - - if (analysisContainer != null) - { - HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisContainer.AnalysisSettings); - DrawableRuleset.Playfield.AddAnalysisContainer(analysisContainer); - } } protected override void PrepareReplay() From 992a0da95727c3d574289d6b3664d2be0729166c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:43:33 +0900 Subject: [PATCH 503/579] Rename classes slightly --- .../TestSceneOsuAnalysisContainer.cs | 8 ++++---- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- .../{OsuAnalysisContainer.cs => ReplayAnalysisOverlay.cs} | 8 ++++---- .../{OsuAnalysisSettings.cs => ReplayAnalysisSettings.cs} | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game.Rulesets.Osu/UI/{OsuAnalysisContainer.cs => ReplayAnalysisOverlay.cs} (96%) rename osu.Game.Rulesets.Osu/UI/{OsuAnalysisSettings.cs => ReplayAnalysisSettings.cs} (93%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 536de8b41ac0..548e40487b42 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public partial class TestSceneOsuAnalysisContainer : OsuTestScene { - private TestOsuAnalysisContainer analysisContainer = null!; + private TestReplayAnalysisOverlay analysisContainer = null!; [SetUpSteps] public void SetUpSteps() @@ -32,7 +32,7 @@ public void SetUpSteps() Children = new Drawable[] { drawableRuleset, - analysisContainer = new TestOsuAnalysisContainer(fabricateReplay(), drawableRuleset), + analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay(), drawableRuleset), }; }); } @@ -96,9 +96,9 @@ private Replay fabricateReplay() return new Replay { Frames = frames }; } - private partial class TestOsuAnalysisContainer : OsuAnalysisContainer + private partial class TestReplayAnalysisOverlay : ReplayAnalysisOverlay { - public TestOsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + public TestReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) : base(replay, drawableRuleset) { } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index ba0768db5dea..ee16fa91f41e 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -39,7 +39,7 @@ public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList protected override void LoadComplete() { if (HasReplayLoaded.Value) - LoadComponentAsync(new OsuAnalysisContainer(ReplayScore.Replay, this), PlayfieldAdjustmentContainer.Add); + LoadComponentAsync(new ReplayAnalysisOverlay(ReplayScore.Replay, this), PlayfieldAdjustmentContainer.Add); base.LoadComplete(); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs similarity index 96% rename from osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 19e53944c964..521f67a77cd2 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -19,18 +19,18 @@ namespace osu.Game.Rulesets.Osu.UI { - public partial class OsuAnalysisContainer : CompositeDrawable + public partial class ReplayAnalysisOverlay : CompositeDrawable { protected readonly HitMarkersContainer HitMarkers; protected readonly AimMarkersContainer AimMarkers; protected readonly AimLinesContainer AimLines; - public OsuAnalysisSettings Settings = null!; + public ReplayAnalysisSettings Settings = null!; private readonly Replay replay; private readonly DrawableRuleset drawableRuleset; - public OsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) + public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) { this.replay = replay; this.drawableRuleset = drawableRuleset; @@ -46,7 +46,7 @@ public OsuAnalysisContainer(Replay replay, DrawableRuleset drawableRuleset) [BackgroundDependencyLoader] private void load() { - AddInternal(Settings = new OsuAnalysisSettings()); + AddInternal(Settings = new ReplayAnalysisSettings()); LoadReplay(); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs similarity index 93% rename from osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index 5419d4e17ae7..87180e155b37 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.UI { - public partial class OsuAnalysisSettings : PlayerSettingsGroup + public partial class ReplayAnalysisSettings : PlayerSettingsGroup { [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HitMarkersEnabled { get; } = new BindableBool(); @@ -23,7 +23,7 @@ public partial class OsuAnalysisSettings : PlayerSettingsGroup [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool CursorHideEnabled { get; } = new BindableBool(); - public OsuAnalysisSettings() + public ReplayAnalysisSettings() : base("Analysis Settings") { } From cc3d220f6f421603fda86800025fe98a859c7c8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 18:58:19 +0900 Subject: [PATCH 504/579] Allow settings to be added to replay HUD from ruleset --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 16 +++++++--------- .../UI/ReplayAnalysisOverlay.cs | 5 +++-- osu.Game/Screens/Play/ReplayPlayer.cs | 10 ++++++++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index ee16fa91f41e..880b2dbe6f35 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,11 +1,10 @@ // 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; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -31,20 +30,19 @@ public partial class DrawableOsuRuleset : DrawableRuleset public new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; - public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) : base(ruleset, beatmap, mods) { } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(ReplayPlayer? replayPlayer) { - if (HasReplayLoaded.Value) - LoadComponentAsync(new ReplayAnalysisOverlay(ReplayScore.Replay, this), PlayfieldAdjustmentContainer.Add); - - base.LoadComplete(); + if (replayPlayer != null) + PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay, this)); } - public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) => null; + public override DrawableHitObject? CreateDrawableRepresentation(OsuHitObject h) => null; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 521f67a77cd2..398ab54981c9 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -44,9 +45,9 @@ public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) } [BackgroundDependencyLoader] - private void load() + private void load(ReplayPlayer replayPlayer) { - AddInternal(Settings = new ReplayAnalysisSettings()); + replayPlayer.AddSettings(Settings = new ReplayAnalysisSettings()); LoadReplay(); } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index ff60dbc0d0d1..0c125264a1a1 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -55,6 +55,16 @@ public ReplayPlayer(Func, Score> createScore, Playe this.createScore = createScore; } + /// + /// Add a settings group to the HUD overlay. Intended to be used by rulesets to add replay-specific settings. + /// + /// The settings group to be shown. + public void AddSettings(PlayerSettingsGroup settings) => Schedule(() => + { + settings.Expanded.Value = false; + HUDOverlay.PlayerSettingsOverlay.Add(settings); + }); + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { From 9b81deb3ac95988d72afb96b509eb6785855281d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 19:09:23 +0900 Subject: [PATCH 505/579] Fix settings not working if `ReplayPlayer` is not available --- .../TestSceneOsuAnalysisContainer.cs | 2 ++ .../UI/ReplayAnalysisOverlay.cs | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 548e40487b42..fc2255a9cf3a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -103,6 +103,8 @@ public TestReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) { } + public new ReplayAnalysisSettings Settings => base.Settings; + public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 398ab54981c9..d16f1613703c 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -26,7 +26,7 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable protected readonly AimMarkersContainer AimMarkers; protected readonly AimLinesContainer AimLines; - public ReplayAnalysisSettings Settings = null!; + protected ReplayAnalysisSettings Settings = null!; private readonly Replay replay; private readonly DrawableRuleset drawableRuleset; @@ -45,32 +45,30 @@ public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) } [BackgroundDependencyLoader] - private void load(ReplayPlayer replayPlayer) + private void load(ReplayPlayer? replayPlayer) { - replayPlayer.AddSettings(Settings = new ReplayAnalysisSettings()); + Settings = new ReplayAnalysisSettings(); - LoadReplay(); + if (replayPlayer != null) + replayPlayer.AddSettings(Settings); + else + // only in test + AddInternal(Settings); + + loadReplay(); } protected override void LoadComplete() { base.LoadComplete(); - Settings.HitMarkersEnabled.BindValueChanged(e => toggleHitMarkers(e.NewValue), true); - Settings.AimMarkersEnabled.BindValueChanged(e => toggleAimMarkers(e.NewValue), true); - Settings.AimLinesEnabled.BindValueChanged(e => toggleAimLines(e.NewValue), true); - Settings.CursorHideEnabled.BindValueChanged(e => toggleCursorHidden(e.NewValue), true); + Settings.HitMarkersEnabled.BindValueChanged(enabled => HitMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + Settings.AimMarkersEnabled.BindValueChanged(enabled => AimMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + Settings.AimLinesEnabled.BindValueChanged(enabled => AimLines.FadeTo(enabled.NewValue ? 1 : 0), true); + Settings.CursorHideEnabled.BindValueChanged(enabled => drawableRuleset.Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); } - private void toggleHitMarkers(bool value) => HitMarkers.FadeTo(value ? 1 : 0); - - private void toggleAimMarkers(bool value) => AimMarkers.FadeTo(value ? 1 : 0); - - private void toggleAimLines(bool value) => AimLines.FadeTo(value ? 1 : 0); - - private void toggleCursorHidden(bool value) => drawableRuleset.Playfield.Cursor.FadeTo(value ? 0 : 1); - - protected void LoadReplay() + private void loadReplay() { bool leftHeld = false; bool rightHeld = false; From 6c07b873af174461888006d87420264b8f349109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 19:28:07 +0900 Subject: [PATCH 506/579] Isolate configuration container from analysis overlay --- .../TestSceneOsuAnalysisContainer.cs | 32 ++++++++--------- .../Configuration/OsuRulesetConfigManager.cs | 4 +-- .../UI/DrawableOsuRuleset.cs | 12 +++++-- .../UI/ReplayAnalysisOverlay.cs | 35 ++++++++----------- .../UI/ReplayAnalysisSettings.cs | 9 ++--- 5 files changed, 47 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index fc2255a9cf3a..04fcb36cad7e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -5,14 +5,14 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Replays; -using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; using osuTK; @@ -21,18 +21,20 @@ namespace osu.Game.Rulesets.Osu.Tests public partial class TestSceneOsuAnalysisContainer : OsuTestScene { private TestReplayAnalysisOverlay analysisContainer = null!; + private ReplayAnalysisSettings settings = null!; + + [Cached] + private OsuRulesetConfigManager config = new OsuRulesetConfigManager(null, new OsuRuleset().RulesetInfo); [SetUpSteps] public void SetUpSteps() { AddStep("create analysis container", () => { - DrawableOsuRuleset drawableRuleset = new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()); - Children = new Drawable[] { - drawableRuleset, - analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay(), drawableRuleset), + analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay()), + settings = new ReplayAnalysisSettings(config), }; }); } @@ -40,27 +42,27 @@ public void SetUpSteps() [Test] public void TestHitMarkers() { - AddStep("enable hit markers", () => analysisContainer.Settings.HitMarkersEnabled.Value = true); + AddStep("enable hit markers", () => settings.HitMarkersEnabled.Value = true); AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); - AddStep("disable hit markers", () => analysisContainer.Settings.HitMarkersEnabled.Value = false); + AddStep("disable hit markers", () => settings.HitMarkersEnabled.Value = false); AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); } [Test] public void TestAimMarker() { - AddStep("enable aim markers", () => analysisContainer.Settings.AimMarkersEnabled.Value = true); + AddStep("enable aim markers", () => settings.AimMarkersEnabled.Value = true); AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); - AddStep("disable aim markers", () => analysisContainer.Settings.AimMarkersEnabled.Value = false); + AddStep("disable aim markers", () => settings.AimMarkersEnabled.Value = false); AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); } [Test] public void TestAimLines() { - AddStep("enable aim lines", () => analysisContainer.Settings.AimLinesEnabled.Value = true); + AddStep("enable aim lines", () => settings.AimLinesEnabled.Value = true); AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); - AddStep("disable aim lines", () => analysisContainer.Settings.AimLinesEnabled.Value = false); + AddStep("disable aim lines", () => settings.AimLinesEnabled.Value = false); AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); } @@ -98,13 +100,11 @@ private Replay fabricateReplay() private partial class TestReplayAnalysisOverlay : ReplayAnalysisOverlay { - public TestReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) - : base(replay, drawableRuleset) + public TestReplayAnalysisOverlay(Replay replay) + : base(replay) { } - public new ReplayAnalysisSettings Settings => base.Settings; - public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index 23b7b9c1faa2..df5cd55c33fd 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.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 osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.UI; @@ -11,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Configuration { public class OsuRulesetConfigManager : RulesetConfigManager { - public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + public OsuRulesetConfigManager(SettingsStore? settings, RulesetInfo ruleset, int? variant = null) : base(settings, ruleset, variant) { } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 880b2dbe6f35..09dcd54c3cc3 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class DrawableOsuRuleset : DrawableRuleset { - protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; + private Bindable? cursorHideEnabled; public new OsuInputManager KeyBindingInputManager => (OsuInputManager)base.KeyBindingInputManager; @@ -39,7 +41,13 @@ public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? private void load(ReplayPlayer? replayPlayer) { if (replayPlayer != null) - PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay, this)); + { + PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay)); + replayPlayer.AddSettings(new ReplayAnalysisSettings((OsuRulesetConfigManager)Config)); + + cursorHideEnabled = ((OsuRulesetConfigManager)Config).GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); + cursorHideEnabled.BindValueChanged(enabled => Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); + } } public override DrawableHitObject? CreateDrawableRepresentation(OsuHitObject h) => null; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index d16f1613703c..80fb561ed104 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; @@ -11,10 +12,9 @@ using osu.Framework.Graphics.Pooling; using osu.Game.Replays; using osu.Game.Rulesets.Objects.Pooling; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Skinning.Default; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -22,19 +22,19 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class ReplayAnalysisOverlay : CompositeDrawable { + private BindableBool hitMarkersEnabled { get; } = new BindableBool(); + private BindableBool aimMarkersEnabled { get; } = new BindableBool(); + private BindableBool aimLinesEnabled { get; } = new BindableBool(); + protected readonly HitMarkersContainer HitMarkers; protected readonly AimMarkersContainer AimMarkers; protected readonly AimLinesContainer AimLines; - protected ReplayAnalysisSettings Settings = null!; - private readonly Replay replay; - private readonly DrawableRuleset drawableRuleset; - public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) + public ReplayAnalysisOverlay(Replay replay) { this.replay = replay; - this.drawableRuleset = drawableRuleset; InternalChildren = new Drawable[] { @@ -45,27 +45,22 @@ public ReplayAnalysisOverlay(Replay replay, DrawableRuleset drawableRuleset) } [BackgroundDependencyLoader] - private void load(ReplayPlayer? replayPlayer) + private void load(OsuRulesetConfigManager config) { - Settings = new ReplayAnalysisSettings(); - - if (replayPlayer != null) - replayPlayer.AddSettings(Settings); - else - // only in test - AddInternal(Settings); - loadReplay(); + + config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, hitMarkersEnabled); + config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, aimMarkersEnabled); + config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, aimLinesEnabled); } protected override void LoadComplete() { base.LoadComplete(); - Settings.HitMarkersEnabled.BindValueChanged(enabled => HitMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - Settings.AimMarkersEnabled.BindValueChanged(enabled => AimMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - Settings.AimLinesEnabled.BindValueChanged(enabled => AimLines.FadeTo(enabled.NewValue ? 1 : 0), true); - Settings.CursorHideEnabled.BindValueChanged(enabled => drawableRuleset.Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); + hitMarkersEnabled.BindValueChanged(enabled => HitMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimMarkersEnabled.BindValueChanged(enabled => AimMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimLinesEnabled.BindValueChanged(enabled => AimLines.FadeTo(enabled.NewValue ? 1 : 0), true); } private void loadReplay() diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index 87180e155b37..dd09ee146b21 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class ReplayAnalysisSettings : PlayerSettingsGroup { + private readonly OsuRulesetConfigManager config; + [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HitMarkersEnabled { get; } = new BindableBool(); @@ -23,18 +25,17 @@ public partial class ReplayAnalysisSettings : PlayerSettingsGroup [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool CursorHideEnabled { get; } = new BindableBool(); - public ReplayAnalysisSettings() + public ReplayAnalysisSettings(OsuRulesetConfigManager config) : base("Analysis Settings") { + this.config = config; } [BackgroundDependencyLoader] - private void load(IRulesetConfigCache cache) + private void load() { AddRange(this.CreateSettingsControls()); - var config = (OsuRulesetConfigManager)cache.GetConfigFor(new OsuRuleset())!; - config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, AimLinesEnabled); From abd74ab41cb8e2b6b34c0786bc88105f0b4378f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 19:41:40 +0900 Subject: [PATCH 507/579] Add note about being able to apply a newer update during runtime --- osu.Desktop/Updater/VelopackUpdateManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index 527892413a3f..e550755fffea 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -54,6 +54,8 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific if (localUserInfo?.IsPlaying.Value == true) return false; + // TODO: we should probably be checking if there's a more recent update, rather than shortcutting here. + // Velopack does support this scenario (see https://github.com/ppy/osu/pull/28743#discussion_r1743495975). if (pendingUpdate != null) { // If there is an update pending restart, show the notification to restart again. From 6a309725ed62370accbf45784d15a57af52c00d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 19:50:45 +0900 Subject: [PATCH 508/579] Make test more usable --- .../TestSceneOsuAnalysisContainer.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 04fcb36cad7e..d31b27e00dba 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -33,9 +33,27 @@ public void SetUpSteps() { Children = new Drawable[] { - analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay()), + new OsuPlayfieldAdjustmentContainer + { + Child = analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay()), + }, settings = new ReplayAnalysisSettings(config), }; + + settings.HitMarkersEnabled.Value = false; + settings.AimMarkersEnabled.Value = false; + settings.AimLinesEnabled.Value = false; + }); + } + + [Test] + public void TestEverythingOn() + { + AddStep("enable everything", () => + { + settings.HitMarkersEnabled.Value = true; + settings.AimMarkersEnabled.Value = true; + settings.AimLinesEnabled.Value = true; }); } @@ -76,8 +94,8 @@ private Replay fabricateReplay() for (int i = 0; i < 1000; i++) { - posX = Math.Clamp(posX + random.Next(-10, 11), 0, 500); - posY = Math.Clamp(posY + random.Next(-10, 11), 0, 500); + posX = Math.Clamp(posX + random.Next(-20, 21), 0, 500); + posY = Math.Clamp(posY + random.Next(-20, 21), 0, 500); var actions = new List(); From b7a56c8a4587fd197624c27ef8fba367edc8bebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Sep 2024 13:57:53 +0200 Subject: [PATCH 509/579] Implement "form" text box control --- .../UserInterface/TestSceneFormControls.cs | 61 +++++ .../UserInterface/ThemeComparisonTestScene.cs | 2 +- .../UserInterfaceV2/FormFieldCaption.cs | 68 +++++ .../Graphics/UserInterfaceV2/FormNumberBox.cs | 23 ++ .../Graphics/UserInterfaceV2/FormTextBox.cs | 238 ++++++++++++++++++ 5 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormFieldCaption.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs new file mode 100644 index 000000000000..f5bc40c8692a --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneFormControls : ThemeComparisonTestScene + { + public TestSceneFormControls() + : base(false) + { + } + + protected override Drawable CreateContent() => new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5), + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new FormTextBox + { + Caption = "Artist", + HintText = "Poot artist here!", + PlaceholderText = "Here is an artist", + TabbableContentContainer = this, + }, + new FormTextBox + { + Caption = "Artist", + HintText = "Poot artist here!", + PlaceholderText = "Here is an artist", + Current = { Disabled = true }, + TabbableContentContainer = this, + }, + new FormNumberBox + { + Caption = "Number", + HintText = "Insert your favourite number", + PlaceholderText = "Mine is 42!", + TabbableContentContainer = this, + }, + }, + }, + }, + }; + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs b/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs index 4700ef72d96d..44133d89f8be 100644 --- a/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs +++ b/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs @@ -75,7 +75,7 @@ protected void CreateThemedContent(OverlayColourScheme colourScheme) new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4 + Colour = colourProvider.Background3 }, CreateContent() } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFieldCaption.cs b/osu.Game/Graphics/UserInterfaceV2/FormFieldCaption.cs new file mode 100644 index 000000000000..75c27618e9e4 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormFieldCaption.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormFieldCaption : CompositeDrawable, IHasTooltip + { + private LocalisableString caption; + + public LocalisableString Caption + { + get => caption; + set + { + caption = value; + + if (captionText.IsNotNull()) + captionText.Text = value; + } + } + + private OsuSpriteText captionText = null!; + + public LocalisableString TooltipText { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + captionText = new OsuSpriteText + { + Text = caption, + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Alpha = TooltipText == default ? 0 : 1, + Size = new Vector2(10), + Icon = FontAwesome.Solid.QuestionCircle, + Margin = new MarginPadding { Top = 1, }, + } + }, + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs new file mode 100644 index 000000000000..66f1a45210d0 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormNumberBox : FormTextBox + { + public bool AllowDecimals { get; init; } + + internal override InnerTextBox CreateTextBox() => new InnerNumberBox + { + AllowDecimals = AllowDecimals, + }; + + internal partial class InnerNumberBox : InnerTextBox + { + public bool AllowDecimals { get; init; } + + protected override bool CanAddCharacter(char character) + => char.IsAsciiDigit(character) || (AllowDecimals && character == '.'); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs new file mode 100644 index 000000000000..985eb74662c2 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -0,0 +1,238 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormTextBox : CompositeDrawable, IHasCurrentValue + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private bool readOnly; + + public bool ReadOnly + { + get => readOnly; + set + { + readOnly = value; + + if (textBox.IsNotNull()) + updateState(); + } + } + + private CompositeDrawable? tabbableContentContainer; + + public CompositeDrawable? TabbableContentContainer + { + set + { + tabbableContentContainer = value; + + if (textBox.IsNotNull()) + textBox.TabbableContentContainer = tabbableContentContainer; + } + } + + public event TextBox.OnCommitHandler? OnCommit; + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public LocalisableString Caption { get; init; } + public LocalisableString HintText { get; init; } + public LocalisableString PlaceholderText { get; init; } + + private Box background = null!; + private Box flashLayer = null!; + private InnerTextBox textBox = null!; + private FormFieldCaption caption = null!; + private IFocusManager focusManager = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + Height = 50; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Transparent, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(9), + Children = new Drawable[] + { + caption = new FormFieldCaption + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Caption = Caption, + TooltipText = HintText, + }, + textBox = CreateTextBox().With(t => + { + t.Anchor = Anchor.BottomRight; + t.Origin = Anchor.BottomRight; + t.RelativeSizeAxes = Axes.X; + t.Width = 1; + t.PlaceholderText = PlaceholderText; + t.Current = Current; + t.CommitOnFocusLost = true; + t.OnCommit += (textBox, newText) => + { + OnCommit?.Invoke(textBox, newText); + + if (!current.Disabled && !ReadOnly) + { + flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark1.Opacity(0), colourProvider.Dark2); + flashLayer.FadeOutFromOne(800, Easing.OutQuint); + } + }; + t.OnInputError = () => + { + flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3); + flashLayer.FadeOutFromOne(200, Easing.OutQuint); + }; + t.TabbableContentContainer = tabbableContentContainer; + }), + }, + }, + }; + } + + internal virtual InnerTextBox CreateTextBox() => new InnerTextBox(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + focusManager = GetContainingFocusManager()!; + textBox.Focused.BindValueChanged(_ => updateState()); + current.BindDisabledChanged(_ => updateState(), true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override bool OnClick(ClickEvent e) + { + focusManager.ChangeFocus(textBox); + return true; + } + + private void updateState() + { + bool disabled = Current.Disabled || ReadOnly; + + textBox.ReadOnly = disabled; + textBox.Alpha = 1; + + caption.Colour = disabled ? colourProvider.Foreground1 : colourProvider.Content2; + textBox.Colour = disabled ? colourProvider.Foreground1 : colourProvider.Content1; + + if (!disabled) + { + BorderThickness = IsHovered || textBox.Focused.Value ? 3 : 0; + BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4; + + if (textBox.Focused.Value) + background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3); + else if (IsHovered) + background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4); + else + background.Colour = colourProvider.Background5; + } + else + { + background.Colour = colourProvider.Background4; + } + } + + internal partial class InnerTextBox : OsuTextBox + { + public BindableBool Focused { get; } = new BindableBool(); + + public Action? OnInputError { get; set; } + + protected override float LeftRightPadding => 0; + + [BackgroundDependencyLoader] + private void load() + { + Height = 16; + TextContainer.Height = 1; + Masking = false; + BackgroundUnfocused = BackgroundFocused = BackgroundCommit = Colour4.Transparent; + } + + protected override SpriteText CreatePlaceholder() => base.CreatePlaceholder().With(t => t.Margin = default); + + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + Focused.Value = true; + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + + Focused.Value = false; + } + + protected override void NotifyInputError() + { + PlayFeedbackSample(FeedbackSampleType.TextInvalid); + // base call intentionally suppressed + OnInputError?.Invoke(); + } + } + } +} From 6fc60908c01816a986792e31918de381944342e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 01:00:23 +0900 Subject: [PATCH 510/579] Trigger request failure on receiving a null response for a typed `APIRequest` --- osu.Game/Online/API/APIRequest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 45ebbcd76dc7..5cbe9040badf 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -46,6 +46,9 @@ protected override void PostProcess() Response = ((OsuJsonWebRequest)WebRequest).ResponseObject; Logger.Log($"{GetType().ReadableName()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network); } + + if (Response == null) + TriggerFailure(new ArgumentNullException(nameof(Response))); } internal void TriggerSuccess(T result) @@ -152,6 +155,8 @@ public void Perform() PostProcess(); + if (isFailing) return; + TriggerSuccess(); } From dcb463acafddefe68c2ed90fab193f599c9458f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 20:07:31 +0900 Subject: [PATCH 511/579] Split out classes and avoid weird configuration stuff --- .../Skinning/Default/HitMarker.cs | 74 -------- .../UI/ReplayAnalysis/AimLinesContainer.cs | 70 ++++++++ .../UI/ReplayAnalysis/AimMarkersContainer.cs | 20 +++ .../UI/ReplayAnalysis/AimPointEntry.cs | 20 +++ .../UI/ReplayAnalysis/HitMarker.cs | 27 +++ .../UI/ReplayAnalysis/HitMarkerEntry.cs | 18 ++ .../UI/ReplayAnalysis/HitMarkerLeftClick.cs | 49 ++++++ .../UI/ReplayAnalysis/HitMarkerMovement.cs | 50 ++++++ .../UI/ReplayAnalysis/HitMarkerRightClick.cs | 49 ++++++ .../UI/ReplayAnalysis/HitMarkersContainer.cs | 28 +++ .../UI/ReplayAnalysisOverlay.cs | 160 +----------------- 11 files changed, 334 insertions(+), 231 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs deleted file mode 100644 index fe662470bcd0..000000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Skinning.Default -{ - public partial class HitMarker : CompositeDrawable - { - public HitMarker(OsuAction? action) - { - var (colour, length, hasBorder) = getConfig(action); - - if (hasBorder) - { - InternalChildren = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) - } - }; - } - - AddRangeInternal(new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Colour = colour - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - Colour = colour - } - }); - } - - private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) - { - switch (action) - { - case OsuAction.LeftButton: - return (Colour4.Orange, 20, true); - - case OsuAction.RightButton: - return (Colour4.LightGreen, 20, true); - - default: - return (Colour4.Gray.Opacity(0.5F), 8, false); - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs new file mode 100644 index 000000000000..db747e277506 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Performance; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class AimLinesContainer : Path + { + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); + + public AimLinesContainer() + { + lifetimeManager.EntryBecameAlive += entryBecameAlive; + lifetimeManager.EntryBecameDead += entryBecameDead; + + PathRadius = 1f; + Colour = new Color4(255, 255, 255, 127); + } + + protected override void Update() + { + base.Update(); + + lifetimeManager.Update(Time.Current); + } + + public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); + + public void Clear() => lifetimeManager.ClearEntries(); + + private void entryBecameAlive(LifetimeEntry entry) + { + aliveEntries.Add((AimPointEntry)entry); + updateVertices(); + } + + private void entryBecameDead(LifetimeEntry entry) + { + aliveEntries.Remove((AimPointEntry)entry); + updateVertices(); + } + + private void updateVertices() + { + ClearVertices(); + + foreach (var entry in aliveEntries) + { + AddVertex(entry.Position); + } + } + + private sealed class AimLinePointComparator : IComparer + { + public int Compare(AimPointEntry? x, AimPointEntry? y) + { + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); + + return x.LifetimeStart.CompareTo(y.LifetimeStart); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs new file mode 100644 index 000000000000..76e88ad5b0f1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer + { + private readonly DrawablePool pool; + + public AimMarkersContainer() + { + AddInternal(pool = new DrawablePool(80)); + } + + protected override HitMarker GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs new file mode 100644 index 000000000000..948568eb1286 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Performance; +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class AimPointEntry : LifetimeEntry + { + public Vector2 Position { get; } + + public AimPointEntry(double time, Vector2 position) + { + LifetimeStart = time; + LifetimeEnd = time + 1_000; + Position = position; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs new file mode 100644 index 000000000000..339bdae5dae5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarker : PoolableDrawableWithLifetime + { + public HitMarker() + { + Origin = Anchor.Centre; + } + + protected override void OnApply(AimPointEntry entry) + { + Position = entry.Position; + + using (BeginAbsoluteSequence(LifetimeStart)) + Show(); + + using (BeginAbsoluteSequence(LifetimeEnd - 200)) + this.FadeOut(200); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs new file mode 100644 index 000000000000..80c268910d02 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkerEntry : AimPointEntry + { + public bool IsLeftMarker { get; } + + public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker) + : base(lifetimeStart, position) + { + IsLeftMarker = isLeftMarker; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs new file mode 100644 index 000000000000..988fc283714a --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs @@ -0,0 +1,49 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkerLeftClick : HitMarker + { + public HitMarkerLeftClick() + { + const float length = 20; + + Colour = Color4.OrangeRed; + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 90, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 90, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs new file mode 100644 index 000000000000..4f9b6f879069 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs @@ -0,0 +1,50 @@ +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkerMovement : HitMarker + { + public HitMarkerMovement() + { + const float length = 5; + + Colour = Color4.Gray.Opacity(0.4f); + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 90, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 90, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs new file mode 100644 index 000000000000..32cdd2d0b51f --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs @@ -0,0 +1,49 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkerRightClick : HitMarker + { + public HitMarkerRightClick() + { + const float length = 20; + + Colour = Color4.GreenYellow; + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 90, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 90, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs new file mode 100644 index 000000000000..fcc5fa28c219 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer + { + private readonly DrawablePool leftPool; + private readonly DrawablePool rightPool; + + public HitMarkersContainer() + { + AddInternal(leftPool = new DrawablePool(15)); + AddInternal(rightPool = new DrawablePool(15)); + } + + protected override HitMarker GetDrawable(HitMarkerEntry entry) + { + if (entry.IsLeftMarker) + return leftPool.Get(d => d.Apply(entry)); + + return rightPool.Get(d => d.Apply(entry)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 80fb561ed104..2cc194847422 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -1,22 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; -using osu.Framework.Graphics.Performance; -using osu.Framework.Graphics.Pooling; using osu.Game.Replays; -using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.Skinning.Default; -using osuTK; -using osuTK.Graphics; +using osu.Game.Rulesets.Osu.UI.ReplayAnalysis; namespace osu.Game.Rulesets.Osu.UI { @@ -34,6 +26,8 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable public ReplayAnalysisOverlay(Replay replay) { + RelativeSizeAxes = Axes.Both; + this.replay = replay; InternalChildren = new Drawable[] @@ -95,153 +89,5 @@ private void loadReplay() } } } - - protected partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer - { - private readonly HitMarkerPool leftPool; - private readonly HitMarkerPool rightPool; - - public HitMarkersContainer() - { - AddInternal(leftPool = new HitMarkerPool(OsuAction.LeftButton, 15)); - AddInternal(rightPool = new HitMarkerPool(OsuAction.RightButton, 15)); - } - - protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry)); - } - - protected partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer - { - private readonly HitMarkerPool pool; - - public AimMarkersContainer() - { - AddInternal(pool = new HitMarkerPool(null, 80)); - } - - protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); - } - - protected partial class AimLinesContainer : Path - { - private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); - - public AimLinesContainer() - { - lifetimeManager.EntryBecameAlive += entryBecameAlive; - lifetimeManager.EntryBecameDead += entryBecameDead; - - PathRadius = 1f; - Colour = new Color4(255, 255, 255, 127); - } - - protected override void Update() - { - base.Update(); - - lifetimeManager.Update(Time.Current); - } - - public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); - - public void Clear() => lifetimeManager.ClearEntries(); - - private void entryBecameAlive(LifetimeEntry entry) - { - aliveEntries.Add((AimPointEntry)entry); - updateVertices(); - } - - private void entryBecameDead(LifetimeEntry entry) - { - aliveEntries.Remove((AimPointEntry)entry); - updateVertices(); - } - - private void updateVertices() - { - ClearVertices(); - - foreach (var entry in aliveEntries) - { - AddVertex(entry.Position); - } - } - - private sealed class AimLinePointComparator : IComparer - { - public int Compare(AimPointEntry? x, AimPointEntry? y) - { - ArgumentNullException.ThrowIfNull(x); - ArgumentNullException.ThrowIfNull(y); - - return x.LifetimeStart.CompareTo(y.LifetimeStart); - } - } - } - - protected partial class HitMarkerDrawable : PoolableDrawableWithLifetime - { - /// - /// This constructor only exists to meet the new() type constraint of . - /// - public HitMarkerDrawable() - { - } - - public HitMarkerDrawable(OsuAction? action) - { - Origin = Anchor.Centre; - InternalChild = new HitMarker(action); - } - - protected override void OnApply(AimPointEntry entry) - { - Position = entry.Position; - - using (BeginAbsoluteSequence(LifetimeStart)) - Show(); - - using (BeginAbsoluteSequence(LifetimeEnd - 200)) - this.FadeOut(200); - } - } - - protected partial class HitMarkerPool : DrawablePool - { - private readonly OsuAction? action; - - public HitMarkerPool(OsuAction? action, int initialSize) - : base(initialSize) - { - this.action = action; - } - - protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(action); - } - - protected partial class AimPointEntry : LifetimeEntry - { - public Vector2 Position { get; } - - public AimPointEntry(double time, Vector2 position) - { - LifetimeStart = time; - LifetimeEnd = time + 1_000; - Position = position; - } - } - - protected partial class HitMarkerEntry : AimPointEntry - { - public bool IsLeftMarker { get; } - - public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker) - : base(lifetimeStart, position) - { - IsLeftMarker = isLeftMarker; - } - } } } From 7f9a98a7aa01307c15ee48d3d74408482b58d6a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 20:17:22 +0900 Subject: [PATCH 512/579] More renames --- .../TestSceneOsuAnalysisContainer.cs | 6 ++--- ...rsContainer.cs => ClickMarkerContainer.cs} | 4 +-- ...ontainer.cs => MovementMarkerContainer.cs} | 4 +-- ...sContainer.cs => MovementPathContainer.cs} | 6 ++--- .../UI/ReplayAnalysisOverlay.cs | 26 +++++++++---------- 5 files changed, 22 insertions(+), 24 deletions(-) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarkersContainer.cs => ClickMarkerContainer.cs} (85%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{AimMarkersContainer.cs => MovementMarkerContainer.cs} (78%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{AimLinesContainer.cs => MovementPathContainer.cs} (92%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index d31b27e00dba..fb6d59ddbaa9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -123,9 +123,9 @@ public TestReplayAnalysisOverlay(Replay replay) { } - public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any(); - public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any(); - public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1; + public bool HitMarkersVisible => ClickMarkers.Alpha > 0 && ClickMarkers.Entries.Any(); + public bool AimMarkersVisible => MovementMarkers.Alpha > 0 && MovementMarkers.Entries.Any(); + public bool AimLinesVisible => MovementPath.Alpha > 0 && MovementPath.Vertices.Count > 1; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs similarity index 85% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs index fcc5fa28c219..be56e26caf64 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkersContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs @@ -6,12 +6,12 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer + public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer { private readonly DrawablePool leftPool; private readonly DrawablePool rightPool; - public HitMarkersContainer() + public ClickMarkerContainer() { AddInternal(leftPool = new DrawablePool(15)); AddInternal(rightPool = new DrawablePool(15)); diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs similarity index 78% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs index 76e88ad5b0f1..8c2bdd590804 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimMarkersContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs @@ -6,11 +6,11 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer + public partial class MovementMarkerContainer : PooledDrawableWithLifetimeContainer { private readonly DrawablePool pool; - public AimMarkersContainer() + public MovementMarkerContainer() { AddInternal(pool = new DrawablePool(80)); } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs similarity index 92% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index db747e277506..58c5993f349f 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimLinesContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -9,12 +9,12 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class AimLinesContainer : Path + public partial class MovementPathContainer : Path { private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); - public AimLinesContainer() + public MovementPathContainer() { lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; @@ -32,8 +32,6 @@ protected override void Update() public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); - public void Clear() => lifetimeManager.ClearEntries(); - private void entryBecameAlive(LifetimeEntry entry) { aliveEntries.Add((AimPointEntry)entry); diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 2cc194847422..d33d468c7e3b 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -18,9 +18,9 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable private BindableBool aimMarkersEnabled { get; } = new BindableBool(); private BindableBool aimLinesEnabled { get; } = new BindableBool(); - protected readonly HitMarkersContainer HitMarkers; - protected readonly AimMarkersContainer AimMarkers; - protected readonly AimLinesContainer AimLines; + protected readonly ClickMarkerContainer ClickMarkers; + protected readonly MovementMarkerContainer MovementMarkers; + protected readonly MovementPathContainer MovementPath; private readonly Replay replay; @@ -32,9 +32,9 @@ public ReplayAnalysisOverlay(Replay replay) InternalChildren = new Drawable[] { - HitMarkers = new HitMarkersContainer(), - AimLines = new AimLinesContainer(), - AimMarkers = new AimMarkersContainer(), + ClickMarkers = new ClickMarkerContainer(), + MovementPath = new MovementPathContainer(), + MovementMarkers = new MovementMarkerContainer(), }; } @@ -52,9 +52,9 @@ protected override void LoadComplete() { base.LoadComplete(); - hitMarkersEnabled.BindValueChanged(enabled => HitMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimMarkersEnabled.BindValueChanged(enabled => AimMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimLinesEnabled.BindValueChanged(enabled => AimLines.FadeTo(enabled.NewValue ? 1 : 0), true); + hitMarkersEnabled.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimMarkersEnabled.BindValueChanged(enabled => MovementMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimLinesEnabled.BindValueChanged(enabled => MovementPath.FadeTo(enabled.NewValue ? 1 : 0), true); } private void loadReplay() @@ -66,8 +66,8 @@ private void loadReplay() { var osuFrame = (OsuReplayFrame)frame; - AimMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); - AimLines.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + MovementMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + MovementPath.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); @@ -76,7 +76,7 @@ private void loadReplay() leftHeld = false; else if (!leftHeld && leftButton) { - HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); + ClickMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); leftHeld = true; } @@ -84,7 +84,7 @@ private void loadReplay() rightHeld = false; else if (!rightHeld && rightButton) { - HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); + ClickMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); rightHeld = true; } } From a4a37c5ba64773ef5cae4626ca76220ca66ce87d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 20:25:29 +0900 Subject: [PATCH 513/579] Simplify lifetime entries and stuff --- ...AimPointEntry.cs => AnalysisFrameEntry.cs} | 7 ++- .../UI/ReplayAnalysis/ClickMarkerContainer.cs | 16 ++---- .../UI/ReplayAnalysis/HitMarker.cs | 4 +- ...itMarkerLeftClick.cs => HitMarkerClick.cs} | 37 +++++++++++--- .../UI/ReplayAnalysis/HitMarkerEntry.cs | 18 ------- .../UI/ReplayAnalysis/HitMarkerRightClick.cs | 49 ------------------- .../ReplayAnalysis/MovementMarkerContainer.cs | 4 +- .../ReplayAnalysis/MovementPathContainer.cs | 22 ++++++--- .../UI/ReplayAnalysisOverlay.cs | 8 +-- 9 files changed, 61 insertions(+), 104 deletions(-) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{AimPointEntry.cs => AnalysisFrameEntry.cs} (66%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarkerLeftClick.cs => HitMarkerClick.cs} (55%) delete mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs delete mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs similarity index 66% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs index 948568eb1286..ca11e6aff18c 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AimPointEntry.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs @@ -6,15 +6,18 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class AimPointEntry : LifetimeEntry + public partial class AnalysisFrameEntry : LifetimeEntry { + public OsuAction? Action { get; } + public Vector2 Position { get; } - public AimPointEntry(double time, Vector2 position) + public AnalysisFrameEntry(double time, Vector2 position, OsuAction? action = null) { LifetimeStart = time; LifetimeEnd = time + 1_000; Position = position; + Action = action; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs index be56e26caf64..3de1a70d7c84 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs @@ -6,23 +6,15 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer + public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer { - private readonly DrawablePool leftPool; - private readonly DrawablePool rightPool; + private readonly DrawablePool clickMarkerPool; public ClickMarkerContainer() { - AddInternal(leftPool = new DrawablePool(15)); - AddInternal(rightPool = new DrawablePool(15)); + AddInternal(clickMarkerPool = new DrawablePool(30)); } - protected override HitMarker GetDrawable(HitMarkerEntry entry) - { - if (entry.IsLeftMarker) - return leftPool.Get(d => d.Apply(entry)); - - return rightPool.Get(d => d.Apply(entry)); - } + protected override HitMarker GetDrawable(AnalysisFrameEntry entry) => clickMarkerPool.Get(d => d.Apply(entry)); } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs index 339bdae5dae5..e2ecba44fc5b 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs @@ -6,14 +6,14 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarker : PoolableDrawableWithLifetime + public partial class HitMarker : PoolableDrawableWithLifetime { public HitMarker() { Origin = Anchor.Centre; } - protected override void OnApply(AimPointEntry entry) + protected override void OnApply(AnalysisFrameEntry entry) { Position = entry.Position; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs similarity index 55% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs index 988fc283714a..4652ea341654 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerLeftClick.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs @@ -1,17 +1,18 @@ +using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarkerLeftClick : HitMarker + public partial class HitMarkerClick : HitMarker { - public HitMarkerLeftClick() + public HitMarkerClick() { const float length = 20; - - Colour = Color4.OrangeRed; + const float border_size = 3; InternalChildren = new Drawable[] { @@ -19,14 +20,14 @@ public HitMarkerLeftClick() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(3, length), + Size = new Vector2(border_size, length + border_size), Colour = Colour4.Black.Opacity(0.5F) }, new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(3, length), + Size = new Vector2(border_size, length + border_size), Rotation = 90, Colour = Colour4.Black.Opacity(0.5F) }, @@ -45,5 +46,27 @@ public HitMarkerLeftClick() } }; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + protected override void OnApply(AnalysisFrameEntry entry) + { + base.OnApply(entry); + + switch (entry.Action) + { + case OsuAction.LeftButton: + Colour = colours.BlueLight; + break; + + case OsuAction.RightButton: + Colour = colours.YellowLight; + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs deleted file mode 100644 index 80c268910d02..000000000000 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerEntry.cs +++ /dev/null @@ -1,18 +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 osuTK; - -namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis -{ - public partial class HitMarkerEntry : AimPointEntry - { - public bool IsLeftMarker { get; } - - public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker) - : base(lifetimeStart, position) - { - IsLeftMarker = isLeftMarker; - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs deleted file mode 100644 index 32cdd2d0b51f..000000000000 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerRightClick.cs +++ /dev/null @@ -1,49 +0,0 @@ -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis -{ - public partial class HitMarkerRightClick : HitMarker - { - public HitMarkerRightClick() - { - const float length = 20; - - Colour = Color4.GreenYellow; - - InternalChildren = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - } - }; - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs index 8c2bdd590804..d52f54650d58 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs @@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class MovementMarkerContainer : PooledDrawableWithLifetimeContainer + public partial class MovementMarkerContainer : PooledDrawableWithLifetimeContainer { private readonly DrawablePool pool; @@ -15,6 +15,6 @@ public MovementMarkerContainer() AddInternal(pool = new DrawablePool(80)); } - protected override HitMarker GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry)); + protected override HitMarker GetDrawable(AnalysisFrameEntry entry) => pool.Get(d => d.Apply(entry)); } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index 58c5993f349f..1d5215703663 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -3,16 +3,17 @@ using System; using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; -using osuTK.Graphics; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { public partial class MovementPathContainer : Path { private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); + private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); public MovementPathContainer() { @@ -20,7 +21,12 @@ public MovementPathContainer() lifetimeManager.EntryBecameDead += entryBecameDead; PathRadius = 1f; - Colour = new Color4(255, 255, 255, 127); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Pink2; } protected override void Update() @@ -30,17 +36,17 @@ protected override void Update() lifetimeManager.Update(Time.Current); } - public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry); + public void Add(AnalysisFrameEntry entry) => lifetimeManager.AddEntry(entry); private void entryBecameAlive(LifetimeEntry entry) { - aliveEntries.Add((AimPointEntry)entry); + aliveEntries.Add((AnalysisFrameEntry)entry); updateVertices(); } private void entryBecameDead(LifetimeEntry entry) { - aliveEntries.Remove((AimPointEntry)entry); + aliveEntries.Remove((AnalysisFrameEntry)entry); updateVertices(); } @@ -54,9 +60,9 @@ private void updateVertices() } } - private sealed class AimLinePointComparator : IComparer + private sealed class AimLinePointComparator : IComparer { - public int Compare(AimPointEntry? x, AimPointEntry? y) + public int Compare(AnalysisFrameEntry? x, AnalysisFrameEntry? y) { ArgumentNullException.ThrowIfNull(x); ArgumentNullException.ThrowIfNull(y); diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index d33d468c7e3b..a42625a9c4db 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -66,8 +66,8 @@ private void loadReplay() { var osuFrame = (OsuReplayFrame)frame; - MovementMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); - MovementPath.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position)); + MovementMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); + MovementPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); @@ -76,7 +76,7 @@ private void loadReplay() leftHeld = false; else if (!leftHeld && leftButton) { - ClickMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true)); + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); leftHeld = true; } @@ -84,7 +84,7 @@ private void loadReplay() rightHeld = false; else if (!rightHeld && rightButton) { - ClickMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false)); + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); rightHeld = true; } } From a6ed719454dc3c0c2784f49d4d8ef627e424e9e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 21:04:59 +0900 Subject: [PATCH 514/579] Visual design pass --- .../UI/ReplayAnalysis/HitMarker.cs | 20 ++++++ .../UI/ReplayAnalysis/HitMarkerClick.cs | 66 ++++--------------- .../UI/ReplayAnalysis/HitMarkerMovement.cs | 40 ++++------- .../ReplayAnalysis/MovementPathContainer.cs | 2 +- .../UI/ReplayAnalysisOverlay.cs | 17 +++-- 5 files changed, 61 insertions(+), 84 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs index e2ecba44fc5b..4fa992ff1537 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Pooling; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis @@ -13,6 +15,9 @@ public HitMarker() Origin = Anchor.Centre; } + [Resolved] + private OsuColour colours { get; set; } = null!; + protected override void OnApply(AnalysisFrameEntry entry) { Position = entry.Position; @@ -22,6 +27,21 @@ protected override void OnApply(AnalysisFrameEntry entry) using (BeginAbsoluteSequence(LifetimeEnd - 200)) this.FadeOut(200); + + switch (entry.Action) + { + case OsuAction.LeftButton: + Colour = colours.BlueLight; + break; + + case OsuAction.RightButton: + Colour = colours.YellowLight; + break; + + default: + Colour = colours.Pink2; + break; + } } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs index 4652ea341654..ba41de7caa42 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs @@ -1,9 +1,8 @@ -using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { @@ -11,62 +10,25 @@ public partial class HitMarkerClick : HitMarker { public HitMarkerClick() { - const float length = 20; - const float border_size = 3; - InternalChildren = new Drawable[] { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(border_size, length + border_size), - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box + new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(border_size, length + border_size), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) + Size = new Vector2(15), + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0, + }, }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - } }; } - - [Resolved] - private OsuColour colours { get; set; } = null!; - - protected override void OnApply(AnalysisFrameEntry entry) - { - base.OnApply(entry); - - switch (entry.Action) - { - case OsuAction.LeftButton: - Colour = colours.BlueLight; - break; - - case OsuAction.RightButton: - Colour = colours.YellowLight; - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs index 4f9b6f879069..0cda732b39cf 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs @@ -1,8 +1,7 @@ -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { @@ -10,41 +9,30 @@ public partial class HitMarkerMovement : HitMarker { public HitMarkerMovement() { - const float length = 5; - - Colour = Color4.Gray.Opacity(0.4f); - InternalChildren = new Drawable[] { - new Box + new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(3, length), - Colour = Colour4.Black.Opacity(0.5F) + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1.2f) }, - new Box + new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 90, - Colour = Colour4.Black.Opacity(0.5F) + RelativeSizeAxes = Axes.Both, }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 90, - } }; } + + protected override void OnApply(AnalysisFrameEntry entry) + { + base.OnApply(entry); + + Size = new Vector2(entry.Action != null ? 4 : 3); + } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index 1d5215703663..ff662c4dfa61 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -20,7 +20,7 @@ public MovementPathContainer() lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; - PathRadius = 1f; + PathRadius = 0.5f; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index a42625a9c4db..682842c56f03 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -62,13 +62,12 @@ private void loadReplay() bool leftHeld = false; bool rightHeld = false; + OsuAction? lastAction = null; + foreach (var frame in replay.Frames) { var osuFrame = (OsuReplayFrame)frame; - MovementMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); - MovementPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); - bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton); bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton); @@ -76,17 +75,25 @@ private void loadReplay() leftHeld = false; else if (!leftHeld && leftButton) { - ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); leftHeld = true; + lastAction = OsuAction.LeftButton; + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); } if (rightHeld && !rightButton) rightHeld = false; else if (!rightHeld && rightButton) { - ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); rightHeld = true; + lastAction = OsuAction.RightButton; + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); } + + if (!leftButton && !rightButton) + lastAction = null; + + MovementMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, lastAction)); + MovementPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); } } } From 21146c35015b2aa6bbd9e015b97313061090c75b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 21:16:59 +0900 Subject: [PATCH 515/579] Add back shadow cast --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 09dcd54c3cc3..c6ad10c06269 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -32,6 +32,8 @@ public partial class DrawableOsuRuleset : DrawableRuleset public new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; + protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; + public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) : base(ruleset, beatmap, mods) { @@ -43,9 +45,9 @@ private void load(ReplayPlayer? replayPlayer) if (replayPlayer != null) { PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay)); - replayPlayer.AddSettings(new ReplayAnalysisSettings((OsuRulesetConfigManager)Config)); + replayPlayer.AddSettings(new ReplayAnalysisSettings(Config)); - cursorHideEnabled = ((OsuRulesetConfigManager)Config).GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); + cursorHideEnabled = Config.GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); cursorHideEnabled.BindValueChanged(enabled => Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); } } From 08ebc83a89d25ad869ef372e4735ebfe0dcaefaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 12:07:16 +0900 Subject: [PATCH 516/579] Fix path getting misaligned with negative position values --- .../UI/ReplayAnalysis/MovementPathContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index ff662c4dfa61..dbe87b7ea7de 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; using osu.Game.Graphics; +using osuTK; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { @@ -54,10 +55,19 @@ private void updateVertices() { ClearVertices(); + Vector2 min = Vector2.Zero; + foreach (var entry in aliveEntries) { AddVertex(entry.Position); + if (entry.Position.X < min.X) + min.X = entry.Position.X; + + if (entry.Position.Y < min.Y) + min.Y = entry.Position.Y; } + + Position = min; } private sealed class AimLinePointComparator : IComparer From ee26ff2e2901bbb51cf477b6c1c3289dcc19b9f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 13:07:49 +0900 Subject: [PATCH 517/579] Add out-of-bounds tests to test case --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index fb6d59ddbaa9..b2cb8c546879 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -94,8 +94,8 @@ private Replay fabricateReplay() for (int i = 0; i < 1000; i++) { - posX = Math.Clamp(posX + random.Next(-20, 21), 0, 500); - posY = Math.Clamp(posY + random.Next(-20, 21), 0, 500); + posX = Math.Clamp(posX + random.Next(-20, 21), -100, 600); + posY = Math.Clamp(posY + random.Next(-20, 21), -100, 600); var actions = new List(); From 2d198e57e1ee2c7f7e6f71009c384301c650317c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 14:02:05 +0900 Subject: [PATCH 518/579] Second visual design pass --- .../UI/ReplayAnalysis/HitMarker.cs | 32 ++--------- .../UI/ReplayAnalysis/HitMarkerClick.cs | 57 ++++++++++++++++++- .../UI/ReplayAnalysis/HitMarkerMovement.cs | 38 ++++++++++--- .../ReplayAnalysis/MovementPathContainer.cs | 2 + .../UI/ReplayAnalysisOverlay.cs | 2 +- 5 files changed, 93 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs index 4fa992ff1537..2187fdfe5742 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs @@ -8,40 +8,20 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarker : PoolableDrawableWithLifetime + public abstract partial class HitMarker : PoolableDrawableWithLifetime { - public HitMarker() + [Resolved] + protected OsuColour Colours { get; private set; } = null!; + + [BackgroundDependencyLoader] + private void load() { Origin = Anchor.Centre; } - [Resolved] - private OsuColour colours { get; set; } = null!; - protected override void OnApply(AnalysisFrameEntry entry) { Position = entry.Position; - - using (BeginAbsoluteSequence(LifetimeStart)) - Show(); - - using (BeginAbsoluteSequence(LifetimeEnd - 200)) - this.FadeOut(200); - - switch (entry.Action) - { - case OsuAction.LeftButton: - Colour = colours.BlueLight; - break; - - case OsuAction.RightButton: - Colour = colours.YellowLight; - break; - - default: - Colour = colours.Pink2; - break; - } } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs index ba41de7caa42..a1024d193008 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs @@ -1,3 +1,4 @@ +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -8,17 +9,30 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { public partial class HitMarkerClick : HitMarker { - public HitMarkerClick() + private Container clickDisplay = null!; + + [BackgroundDependencyLoader] + private void load() { InternalChildren = new Drawable[] { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.125f), + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Colour = Colours.Gray5, + }, new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(15), + RelativeSizeAxes = Axes.Both, + Colour = Colours.Gray5, Masking = true, - BorderThickness = 2, + BorderThickness = 2.2f, BorderColour = Color4.White, Child = new Box { @@ -28,7 +42,44 @@ public HitMarkerClick() Alpha = 0, }, }, + clickDisplay = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Colour = Colours.Yellow, + Scale = new Vector2(0.95f), + Width = 0.5f, + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Child = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Width = 2, + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0, + }, + } + } }; + + Size = new Vector2(16); + } + + protected override void OnApply(AnalysisFrameEntry entry) + { + base.OnApply(entry); + + clickDisplay.Alpha = entry.Action != null ? 1 : 0; + clickDisplay.Rotation = entry.Action == OsuAction.LeftButton ? 0 : 180; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs index 0cda732b39cf..b2bbb47c4b94 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs @@ -1,29 +1,47 @@ +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { public partial class HitMarkerMovement : HitMarker { - public HitMarkerMovement() + private Container clickDisplay = null!; + private Circle mainCircle = null!; + + [BackgroundDependencyLoader] + private void load() { InternalChildren = new Drawable[] { - new Circle + mainCircle = new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = OsuColour.Gray(0.2f), RelativeSizeAxes = Axes.Both, - Size = new Vector2(1.2f) + Colour = Colours.Pink2, }, - new Circle + clickDisplay = new Container { + Colour = Colours.Yellow, + Scale = new Vector2(0.8f), Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Masking = true, + Children = new Drawable[] + { + new Circle + { + RelativeSizeAxes = Axes.Both, + Width = 2, + Colour = Color4.White, + }, + }, }, }; } @@ -31,8 +49,12 @@ public HitMarkerMovement() protected override void OnApply(AnalysisFrameEntry entry) { base.OnApply(entry); + Size = new Vector2(entry.Action != null ? 4 : 2.5f); + + mainCircle.Colour = entry.Action != null ? Colours.Gray4 : Colours.Pink2; - Size = new Vector2(entry.Action != null ? 4 : 3); + clickDisplay.Alpha = entry.Action != null ? 1 : 0; + clickDisplay.Rotation = entry.Action == OsuAction.LeftButton ? 0 : 180; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs index dbe87b7ea7de..2ffc6ffe4a90 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Performance; using osu.Game.Graphics; @@ -28,6 +29,7 @@ public MovementPathContainer() private void load(OsuColour colours) { Colour = colours.Pink2; + BackgroundColour = colours.Pink2.Opacity(0); } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 682842c56f03..06a1930fa388 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -32,8 +32,8 @@ public ReplayAnalysisOverlay(Replay replay) InternalChildren = new Drawable[] { - ClickMarkers = new ClickMarkerContainer(), MovementPath = new MovementPathContainer(), + ClickMarkers = new ClickMarkerContainer(), MovementMarkers = new MovementMarkerContainer(), }; } From 4f719b9fec4973a62514c6c8a5619dbae3350203 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 14:28:20 +0900 Subject: [PATCH 519/579] One more rename pass --- .../TestSceneOsuAnalysisContainer.cs | 4 ++-- .../{HitMarker.cs => AnalysisMarker.cs} | 2 +- .../{HitMarkerClick.cs => ClickMarker.cs} | 5 ++++- .../UI/ReplayAnalysis/ClickMarkerContainer.cs | 8 ++++---- ...athContainer.cs => CursorPathContainer.cs} | 4 ++-- .../{HitMarkerMovement.cs => FrameMarker.cs} | 5 ++++- .../UI/ReplayAnalysis/FrameMarkerContainer.cs | 20 +++++++++++++++++++ .../ReplayAnalysis/MovementMarkerContainer.cs | 20 ------------------- .../UI/ReplayAnalysisOverlay.cs | 16 +++++++-------- 9 files changed, 45 insertions(+), 39 deletions(-) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarker.cs => AnalysisMarker.cs} (87%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarkerClick.cs => ClickMarker.cs} (92%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{MovementPathContainer.cs => CursorPathContainer.cs} (96%) rename osu.Game.Rulesets.Osu/UI/ReplayAnalysis/{HitMarkerMovement.cs => FrameMarker.cs} (91%) create mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarkerContainer.cs delete mode 100644 osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index b2cb8c546879..bea4f430eabd 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -124,8 +124,8 @@ public TestReplayAnalysisOverlay(Replay replay) } public bool HitMarkersVisible => ClickMarkers.Alpha > 0 && ClickMarkers.Entries.Any(); - public bool AimMarkersVisible => MovementMarkers.Alpha > 0 && MovementMarkers.Entries.Any(); - public bool AimLinesVisible => MovementPath.Alpha > 0 && MovementPath.Vertices.Count > 1; + public bool AimMarkersVisible => FrameMarkers.Alpha > 0 && FrameMarkers.Entries.Any(); + public bool AimLinesVisible => CursorPath.Alpha > 0 && CursorPath.Vertices.Count > 1; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs similarity index 87% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs index 2187fdfe5742..9b602c88a854 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public abstract partial class HitMarker : PoolableDrawableWithLifetime + public abstract partial class AnalysisMarker : PoolableDrawableWithLifetime { [Resolved] protected OsuColour Colours { get; private set; } = null!; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs similarity index 92% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs index a1024d193008..5386a80d9db9 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerClick.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs @@ -7,7 +7,10 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarkerClick : HitMarker + /// + /// A marker which shows one click, with visuals focusing on the button which was clicked and the precise location of the click. + /// + public partial class ClickMarker : AnalysisMarker { private Container clickDisplay = null!; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs index 3de1a70d7c84..ff9444952131 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarkerContainer.cs @@ -6,15 +6,15 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer + public partial class ClickMarkerContainer : PooledDrawableWithLifetimeContainer { - private readonly DrawablePool clickMarkerPool; + private readonly DrawablePool clickMarkerPool; public ClickMarkerContainer() { - AddInternal(clickMarkerPool = new DrawablePool(30)); + AddInternal(clickMarkerPool = new DrawablePool(30)); } - protected override HitMarker GetDrawable(AnalysisFrameEntry entry) => clickMarkerPool.Get(d => d.Apply(entry)); + protected override AnalysisMarker GetDrawable(AnalysisFrameEntry entry) => clickMarkerPool.Get(d => d.Apply(entry)); } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/CursorPathContainer.cs similarity index 96% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/CursorPathContainer.cs index 2ffc6ffe4a90..1951d467e230 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementPathContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/CursorPathContainer.cs @@ -12,12 +12,12 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class MovementPathContainer : Path + public partial class CursorPathContainer : Path { private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator()); - public MovementPathContainer() + public CursorPathContainer() { lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs similarity index 91% rename from osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs rename to osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs index b2bbb47c4b94..6d4430707516 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/HitMarkerMovement.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs @@ -7,7 +7,10 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { - public partial class HitMarkerMovement : HitMarker + /// + /// A marker which shows one movement frame, include any buttons which are pressed. + /// + public partial class FrameMarker : AnalysisMarker { private Container clickDisplay = null!; private Circle mainCircle = null!; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarkerContainer.cs new file mode 100644 index 000000000000..63aea259f7cf --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarkerContainer.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis +{ + public partial class FrameMarkerContainer : PooledDrawableWithLifetimeContainer + { + private readonly DrawablePool pool; + + public FrameMarkerContainer() + { + AddInternal(pool = new DrawablePool(80)); + } + + protected override AnalysisMarker GetDrawable(AnalysisFrameEntry entry) => pool.Get(d => d.Apply(entry)); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs deleted file mode 100644 index d52f54650d58..000000000000 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/MovementMarkerContainer.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Pooling; -using osu.Game.Rulesets.Objects.Pooling; - -namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis -{ - public partial class MovementMarkerContainer : PooledDrawableWithLifetimeContainer - { - private readonly DrawablePool pool; - - public MovementMarkerContainer() - { - AddInternal(pool = new DrawablePool(80)); - } - - protected override HitMarker GetDrawable(AnalysisFrameEntry entry) => pool.Get(d => d.Apply(entry)); - } -} diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 06a1930fa388..6d52e3d97583 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -19,8 +19,8 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable private BindableBool aimLinesEnabled { get; } = new BindableBool(); protected readonly ClickMarkerContainer ClickMarkers; - protected readonly MovementMarkerContainer MovementMarkers; - protected readonly MovementPathContainer MovementPath; + protected readonly FrameMarkerContainer FrameMarkers; + protected readonly CursorPathContainer CursorPath; private readonly Replay replay; @@ -32,9 +32,9 @@ public ReplayAnalysisOverlay(Replay replay) InternalChildren = new Drawable[] { - MovementPath = new MovementPathContainer(), + CursorPath = new CursorPathContainer(), ClickMarkers = new ClickMarkerContainer(), - MovementMarkers = new MovementMarkerContainer(), + FrameMarkers = new FrameMarkerContainer(), }; } @@ -53,8 +53,8 @@ protected override void LoadComplete() base.LoadComplete(); hitMarkersEnabled.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimMarkersEnabled.BindValueChanged(enabled => MovementMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimLinesEnabled.BindValueChanged(enabled => MovementPath.FadeTo(enabled.NewValue ? 1 : 0), true); + aimMarkersEnabled.BindValueChanged(enabled => FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + aimLinesEnabled.BindValueChanged(enabled => CursorPath.FadeTo(enabled.NewValue ? 1 : 0), true); } private void loadReplay() @@ -92,8 +92,8 @@ private void loadReplay() if (!leftButton && !rightButton) lastAction = null; - MovementMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, lastAction)); - MovementPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); + FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, lastAction)); + CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); } } } From 7390d89c75f45bc86b7e638ed3eddbae3d829ddc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:12:02 +0900 Subject: [PATCH 520/579] Switch to using `CircularProgress` for more consistent sizing Also show both mouse buttons at once on frame markers. --- .../UI/ReplayAnalysis/AnalysisFrameEntry.cs | 4 +- .../UI/ReplayAnalysis/ClickMarker.cs | 52 +++++++++---------- .../UI/ReplayAnalysis/FrameMarker.cs | 48 +++++++++-------- .../UI/ReplayAnalysisOverlay.cs | 9 +--- 4 files changed, 56 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs index ca11e6aff18c..d44def1b6746 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs @@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { public partial class AnalysisFrameEntry : LifetimeEntry { - public OsuAction? Action { get; } + public OsuAction[] Action { get; } public Vector2 Position { get; } - public AnalysisFrameEntry(double time, Vector2 position, OsuAction? action = null) + public AnalysisFrameEntry(double time, Vector2 position, params OsuAction[] action) { LifetimeStart = time; LifetimeEnd = time + 1_000; diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs index 5386a80d9db9..9788ea1aa9bc 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/ClickMarker.cs @@ -1,7 +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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -12,7 +17,8 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis /// public partial class ClickMarker : AnalysisMarker { - private Container clickDisplay = null!; + private CircularProgress leftClickDisplay = null!; + private CircularProgress rightClickDisplay = null!; [BackgroundDependencyLoader] private void load() @@ -45,33 +51,27 @@ private void load() Alpha = 0, }, }, - clickDisplay = new Container + leftClickDisplay = new CircularProgress { - Anchor = Anchor.Centre, + Colour = Colours.Yellow, + Size = new Vector2(0.95f), + Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, + Rotation = 180, + Progress = 0.5f, + InnerRadius = 0.18f, RelativeSizeAxes = Axes.Both, + }, + rightClickDisplay = new CircularProgress + { Colour = Colours.Yellow, - Scale = new Vector2(0.95f), - Width = 0.5f, - Masking = true, - BorderThickness = 2, - BorderColour = Color4.White, - Child = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Width = 2, - Masking = true, - BorderThickness = 2, - BorderColour = Color4.White, - Child = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0, - }, - } - } + Size = new Vector2(0.95f), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Progress = 0.5f, + InnerRadius = 0.18f, + RelativeSizeAxes = Axes.Both, + }, }; Size = new Vector2(16); @@ -81,8 +81,8 @@ protected override void OnApply(AnalysisFrameEntry entry) { base.OnApply(entry); - clickDisplay.Alpha = entry.Action != null ? 1 : 0; - clickDisplay.Rotation = entry.Action == OsuAction.LeftButton ? 0 : 180; + leftClickDisplay.Alpha = entry.Action.Contains(OsuAction.LeftButton) ? 1 : 0; + rightClickDisplay.Alpha = entry.Action.Contains(OsuAction.RightButton) ? 1 : 0; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs index 6d4430707516..35ee1445683a 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/FrameMarker.cs @@ -1,9 +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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis { @@ -12,7 +15,8 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis /// public partial class FrameMarker : AnalysisMarker { - private Container clickDisplay = null!; + private CircularProgress leftClickDisplay = null!; + private CircularProgress rightClickDisplay = null!; private Circle mainCircle = null!; [BackgroundDependencyLoader] @@ -27,24 +31,26 @@ private void load() RelativeSizeAxes = Axes.Both, Colour = Colours.Pink2, }, - clickDisplay = new Container + leftClickDisplay = new CircularProgress { Colour = Colours.Yellow, - Scale = new Vector2(0.8f), - Anchor = Anchor.Centre, + Size = new Vector2(0.8f), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Rotation = 180, + Progress = 0.5f, + InnerRadius = 0.5f, + RelativeSizeAxes = Axes.Both, + }, + rightClickDisplay = new CircularProgress + { + Colour = Colours.Yellow, + Size = new Vector2(0.8f), + Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + Progress = 0.5f, + InnerRadius = 0.5f, RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Masking = true, - Children = new Drawable[] - { - new Circle - { - RelativeSizeAxes = Axes.Both, - Width = 2, - Colour = Color4.White, - }, - }, }, }; } @@ -52,12 +58,12 @@ private void load() protected override void OnApply(AnalysisFrameEntry entry) { base.OnApply(entry); - Size = new Vector2(entry.Action != null ? 4 : 2.5f); + Size = new Vector2(entry.Action.Any() ? 4 : 2.5f); - mainCircle.Colour = entry.Action != null ? Colours.Gray4 : Colours.Pink2; + mainCircle.Colour = entry.Action.Any() ? Colours.Gray4 : Colours.Pink2; - clickDisplay.Alpha = entry.Action != null ? 1 : 0; - clickDisplay.Rotation = entry.Action == OsuAction.LeftButton ? 0 : 180; + leftClickDisplay.Alpha = entry.Action.Contains(OsuAction.LeftButton) ? 1 : 0; + rightClickDisplay.Alpha = entry.Action.Contains(OsuAction.RightButton) ? 1 : 0; } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 6d52e3d97583..eb1ef49e5d89 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -62,8 +62,6 @@ private void loadReplay() bool leftHeld = false; bool rightHeld = false; - OsuAction? lastAction = null; - foreach (var frame in replay.Frames) { var osuFrame = (OsuReplayFrame)frame; @@ -76,7 +74,6 @@ private void loadReplay() else if (!leftHeld && leftButton) { leftHeld = true; - lastAction = OsuAction.LeftButton; ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); } @@ -85,14 +82,10 @@ private void loadReplay() else if (!rightHeld && rightButton) { rightHeld = true; - lastAction = OsuAction.RightButton; ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); } - if (!leftButton && !rightButton) - lastAction = null; - - FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, lastAction)); + FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, osuFrame.Actions.ToArray())); CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); } } From 7983a765ab09c47fd0cf908aa6b758420e98f9ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:12:16 +0900 Subject: [PATCH 521/579] Update test scene to show more button holds (including both buttons sometimes) --- .../TestSceneOsuAnalysisContainer.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index bea4f430eabd..292ecb7fde49 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -90,26 +90,28 @@ private Replay fabricateReplay() var random = new Random(); int posX = 250; int posY = 250; - bool leftOrRight = false; + + var actions = new HashSet(); for (int i = 0; i < 1000; i++) { posX = Math.Clamp(posX + random.Next(-20, 21), -100, 600); posY = Math.Clamp(posY + random.Next(-20, 21), -100, 600); - var actions = new List(); - - if (i % 20 == 0) + if (random.NextDouble() > (actions.Count == 0 ? 0.9 : 0.95)) + { + actions.Add(random.NextDouble() > 0.5 ? OsuAction.LeftButton : OsuAction.RightButton); + } + else if (random.NextDouble() > 0.7) { - actions.Add(leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton); - leftOrRight = !leftOrRight; + actions.Remove(random.NextDouble() > 0.5 ? OsuAction.LeftButton : OsuAction.RightButton); } frames.Add(new OsuReplayFrame { Time = Time.Current + i * 15, Position = new Vector2(posX, posY), - Actions = actions + Actions = actions.ToList(), }); } From 47a9b345ebc52cdecd8e772510014d372f9376c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:15:15 +0900 Subject: [PATCH 522/579] Rename config variables and setting strings --- .../TestSceneOsuAnalysisContainer.cs | 24 +++++++++---------- .../Configuration/OsuRulesetConfigManager.cs | 12 +++++----- .../UI/ReplayAnalysisOverlay.cs | 18 +++++++------- .../UI/ReplayAnalysisSettings.cs | 24 +++++++++---------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index 292ecb7fde49..fb8ac81b2cc9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -40,9 +40,9 @@ public void SetUpSteps() settings = new ReplayAnalysisSettings(config), }; - settings.HitMarkersEnabled.Value = false; - settings.AimMarkersEnabled.Value = false; - settings.AimLinesEnabled.Value = false; + settings.ShowClickMarkers.Value = false; + settings.ShowAimMarkers.Value = false; + settings.ShowCursorPath.Value = false; }); } @@ -51,36 +51,36 @@ public void TestEverythingOn() { AddStep("enable everything", () => { - settings.HitMarkersEnabled.Value = true; - settings.AimMarkersEnabled.Value = true; - settings.AimLinesEnabled.Value = true; + settings.ShowClickMarkers.Value = true; + settings.ShowAimMarkers.Value = true; + settings.ShowCursorPath.Value = true; }); } [Test] public void TestHitMarkers() { - AddStep("enable hit markers", () => settings.HitMarkersEnabled.Value = true); + AddStep("enable hit markers", () => settings.ShowClickMarkers.Value = true); AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); - AddStep("disable hit markers", () => settings.HitMarkersEnabled.Value = false); + AddStep("disable hit markers", () => settings.ShowClickMarkers.Value = false); AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); } [Test] public void TestAimMarker() { - AddStep("enable aim markers", () => settings.AimMarkersEnabled.Value = true); + AddStep("enable aim markers", () => settings.ShowAimMarkers.Value = true); AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); - AddStep("disable aim markers", () => settings.AimMarkersEnabled.Value = false); + AddStep("disable aim markers", () => settings.ShowAimMarkers.Value = false); AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); } [Test] public void TestAimLines() { - AddStep("enable aim lines", () => settings.AimLinesEnabled.Value = true); + AddStep("enable aim lines", () => settings.ShowCursorPath.Value = true); AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); - AddStep("disable aim lines", () => settings.AimLinesEnabled.Value = false); + AddStep("disable aim lines", () => settings.ShowCursorPath.Value = false); AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); } diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index df5cd55c33fd..e6002523b165 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -23,9 +23,9 @@ protected override void InitialiseDefaults() SetDefault(OsuRulesetSetting.ShowCursorRipples, false); SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); - SetDefault(OsuRulesetSetting.ReplayHitMarkersEnabled, false); - SetDefault(OsuRulesetSetting.ReplayAimMarkersEnabled, false); - SetDefault(OsuRulesetSetting.ReplayAimLinesEnabled, false); + SetDefault(OsuRulesetSetting.ReplayClickMarkersEnabled, false); + SetDefault(OsuRulesetSetting.ReplayFrameMarkersEnabled, false); + SetDefault(OsuRulesetSetting.ReplayCursorPathEnabled, false); SetDefault(OsuRulesetSetting.ReplayCursorHideEnabled, false); } } @@ -39,9 +39,9 @@ public enum OsuRulesetSetting PlayfieldBorderStyle, // Replay - ReplayHitMarkersEnabled, - ReplayAimMarkersEnabled, - ReplayAimLinesEnabled, + ReplayClickMarkersEnabled, + ReplayFrameMarkersEnabled, + ReplayCursorPathEnabled, ReplayCursorHideEnabled, } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index eb1ef49e5d89..622c32c51e63 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -14,9 +14,9 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class ReplayAnalysisOverlay : CompositeDrawable { - private BindableBool hitMarkersEnabled { get; } = new BindableBool(); - private BindableBool aimMarkersEnabled { get; } = new BindableBool(); - private BindableBool aimLinesEnabled { get; } = new BindableBool(); + private BindableBool showClickMarkers { get; } = new BindableBool(); + private BindableBool showFrameMarkers { get; } = new BindableBool(); + private BindableBool showCursorPath { get; } = new BindableBool(); protected readonly ClickMarkerContainer ClickMarkers; protected readonly FrameMarkerContainer FrameMarkers; @@ -43,18 +43,18 @@ private void load(OsuRulesetConfigManager config) { loadReplay(); - config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, hitMarkersEnabled); - config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, aimMarkersEnabled); - config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, aimLinesEnabled); + config.BindWith(OsuRulesetSetting.ReplayClickMarkersEnabled, showClickMarkers); + config.BindWith(OsuRulesetSetting.ReplayFrameMarkersEnabled, showFrameMarkers); + config.BindWith(OsuRulesetSetting.ReplayCursorPathEnabled, showCursorPath); } protected override void LoadComplete() { base.LoadComplete(); - hitMarkersEnabled.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimMarkersEnabled.BindValueChanged(enabled => FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - aimLinesEnabled.BindValueChanged(enabled => CursorPath.FadeTo(enabled.NewValue ? 1 : 0), true); + showClickMarkers.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + showFrameMarkers.BindValueChanged(enabled => FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); + showCursorPath.BindValueChanged(enabled => CursorPath.FadeTo(enabled.NewValue ? 1 : 0), true); } private void loadReplay() diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index dd09ee146b21..7daab681805c 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -13,17 +13,17 @@ public partial class ReplayAnalysisSettings : PlayerSettingsGroup { private readonly OsuRulesetConfigManager config; - [SettingSource("Hit markers", SettingControlType = typeof(PlayerCheckbox))] - public BindableBool HitMarkersEnabled { get; } = new BindableBool(); + [SettingSource("Show click markers", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool ShowClickMarkers { get; } = new BindableBool(); - [SettingSource("Aim markers", SettingControlType = typeof(PlayerCheckbox))] - public BindableBool AimMarkersEnabled { get; } = new BindableBool(); + [SettingSource("Show frame markers", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool ShowAimMarkers { get; } = new BindableBool(); - [SettingSource("Aim lines", SettingControlType = typeof(PlayerCheckbox))] - public BindableBool AimLinesEnabled { get; } = new BindableBool(); + [SettingSource("Show cursor path", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool ShowCursorPath { get; } = new BindableBool(); - [SettingSource("Hide cursor", SettingControlType = typeof(PlayerCheckbox))] - public BindableBool CursorHideEnabled { get; } = new BindableBool(); + [SettingSource("Hide gameplay cursor", SettingControlType = typeof(PlayerCheckbox))] + public BindableBool HideSkinCursor { get; } = new BindableBool(); public ReplayAnalysisSettings(OsuRulesetConfigManager config) : base("Analysis Settings") @@ -36,10 +36,10 @@ private void load() { AddRange(this.CreateSettingsControls()); - config.BindWith(OsuRulesetSetting.ReplayHitMarkersEnabled, HitMarkersEnabled); - config.BindWith(OsuRulesetSetting.ReplayAimMarkersEnabled, AimMarkersEnabled); - config.BindWith(OsuRulesetSetting.ReplayAimLinesEnabled, AimLinesEnabled); - config.BindWith(OsuRulesetSetting.ReplayCursorHideEnabled, CursorHideEnabled); + config.BindWith(OsuRulesetSetting.ReplayClickMarkersEnabled, ShowClickMarkers); + config.BindWith(OsuRulesetSetting.ReplayFrameMarkersEnabled, ShowAimMarkers); + config.BindWith(OsuRulesetSetting.ReplayCursorPathEnabled, ShowCursorPath); + config.BindWith(OsuRulesetSetting.ReplayCursorHideEnabled, HideSkinCursor); } } } From 0f01a855afd2a4585065eb41f4ee1ab49cdbc0d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:30:42 +0900 Subject: [PATCH 523/579] Add note about cursor hiding being potentially flaky --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index c6ad10c06269..16edc654a7dd 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -48,6 +48,9 @@ private void load(ReplayPlayer? replayPlayer) replayPlayer.AddSettings(new ReplayAnalysisSettings(Config)); cursorHideEnabled = Config.GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); + + // I have little faith in this working (other things touch cursor visibility) but haven't broken it yet. + // Let's wait for someone to report an issue before spending too much time on it. cursorHideEnabled.BindValueChanged(enabled => Playfield.Cursor.FadeTo(enabled.NewValue ? 0 : 1), true); } } From a1cf67be629e7f3c4dedaac9aadab6b3f0f73598 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 15:53:53 +0900 Subject: [PATCH 524/579] Add setting to adjust replay analysis display length --- .../Configuration/OsuRulesetConfigManager.cs | 2 + .../UI/ReplayAnalysis/AnalysisFrameEntry.cs | 4 +- .../UI/ReplayAnalysisOverlay.cs | 70 +++++++++++++------ .../UI/ReplayAnalysisSettings.cs | 10 +++ 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index e6002523b165..8a8b78b64509 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -27,6 +27,7 @@ protected override void InitialiseDefaults() SetDefault(OsuRulesetSetting.ReplayFrameMarkersEnabled, false); SetDefault(OsuRulesetSetting.ReplayCursorPathEnabled, false); SetDefault(OsuRulesetSetting.ReplayCursorHideEnabled, false); + SetDefault(OsuRulesetSetting.ReplayAnalysisDisplayLength, 750); } } @@ -43,5 +44,6 @@ public enum OsuRulesetSetting ReplayFrameMarkersEnabled, ReplayCursorPathEnabled, ReplayCursorHideEnabled, + ReplayAnalysisDisplayLength, } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs index d44def1b6746..116bccc74705 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisFrameEntry.cs @@ -12,10 +12,10 @@ public partial class AnalysisFrameEntry : LifetimeEntry public Vector2 Position { get; } - public AnalysisFrameEntry(double time, Vector2 position, params OsuAction[] action) + public AnalysisFrameEntry(double time, double displayLength, Vector2 position, params OsuAction[] action) { LifetimeStart = time; - LifetimeEnd = time + 1_000; + LifetimeEnd = time + displayLength; Position = position; Action = action; } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 622c32c51e63..0c257a68c55a 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -17,10 +17,11 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable private BindableBool showClickMarkers { get; } = new BindableBool(); private BindableBool showFrameMarkers { get; } = new BindableBool(); private BindableBool showCursorPath { get; } = new BindableBool(); + private BindableInt displayLength { get; } = new BindableInt(); - protected readonly ClickMarkerContainer ClickMarkers; - protected readonly FrameMarkerContainer FrameMarkers; - protected readonly CursorPathContainer CursorPath; + protected ClickMarkerContainer ClickMarkers = null!; + protected FrameMarkerContainer FrameMarkers = null!; + protected CursorPathContainer CursorPath = null!; private readonly Replay replay; @@ -29,36 +30,65 @@ public ReplayAnalysisOverlay(Replay replay) RelativeSizeAxes = Axes.Both; this.replay = replay; - - InternalChildren = new Drawable[] - { - CursorPath = new CursorPathContainer(), - ClickMarkers = new ClickMarkerContainer(), - FrameMarkers = new FrameMarkerContainer(), - }; } + private bool requireDisplay => showClickMarkers.Value || showFrameMarkers.Value || showCursorPath.Value; + [BackgroundDependencyLoader] private void load(OsuRulesetConfigManager config) { - loadReplay(); - config.BindWith(OsuRulesetSetting.ReplayClickMarkersEnabled, showClickMarkers); config.BindWith(OsuRulesetSetting.ReplayFrameMarkersEnabled, showFrameMarkers); config.BindWith(OsuRulesetSetting.ReplayCursorPathEnabled, showCursorPath); + config.BindWith(OsuRulesetSetting.ReplayAnalysisDisplayLength, displayLength); } protected override void LoadComplete() { base.LoadComplete(); - showClickMarkers.BindValueChanged(enabled => ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - showFrameMarkers.BindValueChanged(enabled => FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0), true); - showCursorPath.BindValueChanged(enabled => CursorPath.FadeTo(enabled.NewValue ? 1 : 0), true); + showClickMarkers.BindValueChanged(enabled => + { + initialise(); + ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0); + }, true); + showFrameMarkers.BindValueChanged(enabled => + { + initialise(); + FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0); + }, true); + showCursorPath.BindValueChanged(enabled => + { + initialise(); + CursorPath.FadeTo(enabled.NewValue ? 1 : 0); + }, true); + displayLength.BindValueChanged(_ => + { + isLoaded = false; + initialise(); + }, true); } - private void loadReplay() + private bool isLoaded; + + private void initialise() { + if (!requireDisplay) + return; + + if (isLoaded) + return; + + isLoaded = true; + + // It's faster to reinitialise the whole drawable stack than use `Clear` on `PooledDrawableWithLifetimeContainer` + InternalChildren = new Drawable[] + { + CursorPath = new CursorPathContainer(), + ClickMarkers = new ClickMarkerContainer(), + FrameMarkers = new FrameMarkerContainer(), + }; + bool leftHeld = false; bool rightHeld = false; @@ -74,7 +104,7 @@ private void loadReplay() else if (!leftHeld && leftButton) { leftHeld = true; - ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.LeftButton)); + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position, OsuAction.LeftButton)); } if (rightHeld && !rightButton) @@ -82,11 +112,11 @@ private void loadReplay() else if (!rightHeld && rightButton) { rightHeld = true; - ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, OsuAction.RightButton)); + ClickMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position, OsuAction.RightButton)); } - FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position, osuFrame.Actions.ToArray())); - CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, osuFrame.Position)); + FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position, osuFrame.Actions.ToArray())); + CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position)); } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index 7daab681805c..6acafb5d3bf5 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -25,6 +25,15 @@ public partial class ReplayAnalysisSettings : PlayerSettingsGroup [SettingSource("Hide gameplay cursor", SettingControlType = typeof(PlayerCheckbox))] public BindableBool HideSkinCursor { get; } = new BindableBool(); + [SettingSource("Display length", SettingControlType = typeof(PlayerSliderBar))] + public BindableInt DisplayLength { get; } = new BindableInt + { + MinValue = 100, + Default = 800, + MaxValue = 2000, + Precision = 100, + }; + public ReplayAnalysisSettings(OsuRulesetConfigManager config) : base("Analysis Settings") { @@ -40,6 +49,7 @@ private void load() config.BindWith(OsuRulesetSetting.ReplayFrameMarkersEnabled, ShowAimMarkers); config.BindWith(OsuRulesetSetting.ReplayCursorPathEnabled, ShowCursorPath); config.BindWith(OsuRulesetSetting.ReplayCursorHideEnabled, HideSkinCursor); + config.BindWith(OsuRulesetSetting.ReplayAnalysisDisplayLength, DisplayLength); } } } From 167e3a337796d925025cba8497300734d6f76a14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 16:01:28 +0900 Subject: [PATCH 525/579] Make loading asynchronous --- .../UI/ReplayAnalysisOverlay.cs | 59 +++++++++++-------- .../UI/ReplayAnalysisSettings.cs | 6 +- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 0c257a68c55a..8a48e811118e 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Replays; @@ -19,9 +21,9 @@ public partial class ReplayAnalysisOverlay : CompositeDrawable private BindableBool showCursorPath { get; } = new BindableBool(); private BindableInt displayLength { get; } = new BindableInt(); - protected ClickMarkerContainer ClickMarkers = null!; - protected FrameMarkerContainer FrameMarkers = null!; - protected CursorPathContainer CursorPath = null!; + protected ClickMarkerContainer? ClickMarkers; + protected FrameMarkerContainer? FrameMarkers; + protected CursorPathContainer? CursorPath; private readonly Replay replay; @@ -47,42 +49,43 @@ protected override void LoadComplete() { base.LoadComplete(); - showClickMarkers.BindValueChanged(enabled => - { - initialise(); - ClickMarkers.FadeTo(enabled.NewValue ? 1 : 0); - }, true); - showFrameMarkers.BindValueChanged(enabled => - { - initialise(); - FrameMarkers.FadeTo(enabled.NewValue ? 1 : 0); - }, true); - showCursorPath.BindValueChanged(enabled => - { - initialise(); - CursorPath.FadeTo(enabled.NewValue ? 1 : 0); - }, true); displayLength.BindValueChanged(_ => { - isLoaded = false; - initialise(); + // Need to fully reload to make this work. + loaded.Invalidate(); }, true); } - private bool isLoaded; + private readonly Cached loaded = new Cached(); + + private CancellationTokenSource? generationCancellationSource; + + protected override void Update() + { + base.Update(); + + if (requireDisplay) + { + initialise(); + + if (ClickMarkers != null) ClickMarkers.Alpha = showClickMarkers.Value ? 1 : 0; + if (FrameMarkers != null) FrameMarkers.Alpha = showFrameMarkers.Value ? 1 : 0; + if (CursorPath != null) CursorPath.Alpha = showCursorPath.Value ? 1 : 0; + } + } private void initialise() { - if (!requireDisplay) + if (loaded.IsValid) return; - if (isLoaded) - return; + loaded.Validate(); - isLoaded = true; + generationCancellationSource?.Cancel(); + generationCancellationSource = new CancellationTokenSource(); // It's faster to reinitialise the whole drawable stack than use `Clear` on `PooledDrawableWithLifetimeContainer` - InternalChildren = new Drawable[] + var newDrawables = new Drawable[] { CursorPath = new CursorPathContainer(), ClickMarkers = new ClickMarkerContainer(), @@ -92,6 +95,8 @@ private void initialise() bool leftHeld = false; bool rightHeld = false; + // This should probably be async as well, but it's a bit of a pain to debounce and everything. + // Let's address concerns when they are raised. foreach (var frame in replay.Frames) { var osuFrame = (OsuReplayFrame)frame; @@ -118,6 +123,8 @@ private void initialise() FrameMarkers.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position, osuFrame.Actions.ToArray())); CursorPath.Add(new AnalysisFrameEntry(osuFrame.Time, displayLength.Value, osuFrame.Position)); } + + LoadComponentsAsync(newDrawables, drawables => InternalChildrenEnumerable = drawables, generationCancellationSource.Token); } } } diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs index 6acafb5d3bf5..dc4730d76ab5 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisSettings.cs @@ -28,10 +28,10 @@ public partial class ReplayAnalysisSettings : PlayerSettingsGroup [SettingSource("Display length", SettingControlType = typeof(PlayerSliderBar))] public BindableInt DisplayLength { get; } = new BindableInt { - MinValue = 100, - Default = 800, + MinValue = 200, MaxValue = 2000, - Precision = 100, + Default = 800, + Precision = 200, }; public ReplayAnalysisSettings(OsuRulesetConfigManager config) From 7136483f85461c73d714a7588cb9f8a6a193632d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Sep 2024 09:45:34 +0200 Subject: [PATCH 526/579] Fix nullability inspections --- .../TestSceneOsuAnalysisContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index fb8ac81b2cc9..d72a34767514 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -125,9 +125,9 @@ public TestReplayAnalysisOverlay(Replay replay) { } - public bool HitMarkersVisible => ClickMarkers.Alpha > 0 && ClickMarkers.Entries.Any(); - public bool AimMarkersVisible => FrameMarkers.Alpha > 0 && FrameMarkers.Entries.Any(); - public bool AimLinesVisible => CursorPath.Alpha > 0 && CursorPath.Vertices.Count > 1; + public bool HitMarkersVisible => ClickMarkers?.Alpha > 0 && ClickMarkers.Entries.Any(); + public bool AimMarkersVisible => FrameMarkers?.Alpha > 0 && FrameMarkers.Entries.Any(); + public bool AimLinesVisible => CursorPath?.Alpha > 0 && CursorPath.Vertices.Count > 1; } } } From b9ddac420171e1ecfbcb4374538d8c1c117a92a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Sep 2024 09:45:37 +0200 Subject: [PATCH 527/579] Fix test failures --- .../TestSceneOsuAnalysisContainer.cs | 12 ++++++------ osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs | 8 +++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs index d72a34767514..184938ceda8b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuAnalysisContainer.cs @@ -61,27 +61,27 @@ public void TestEverythingOn() public void TestHitMarkers() { AddStep("enable hit markers", () => settings.ShowClickMarkers.Value = true); - AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible); + AddUntilStep("hit markers visible", () => analysisContainer.HitMarkersVisible); AddStep("disable hit markers", () => settings.ShowClickMarkers.Value = false); - AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible); + AddUntilStep("hit markers not visible", () => !analysisContainer.HitMarkersVisible); } [Test] public void TestAimMarker() { AddStep("enable aim markers", () => settings.ShowAimMarkers.Value = true); - AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible); + AddUntilStep("aim markers visible", () => analysisContainer.AimMarkersVisible); AddStep("disable aim markers", () => settings.ShowAimMarkers.Value = false); - AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible); + AddUntilStep("aim markers not visible", () => !analysisContainer.AimMarkersVisible); } [Test] public void TestAimLines() { AddStep("enable aim lines", () => settings.ShowCursorPath.Value = true); - AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible); + AddUntilStep("aim lines visible", () => analysisContainer.AimLinesVisible); AddStep("disable aim lines", () => settings.ShowCursorPath.Value = false); - AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible); + AddUntilStep("aim lines not visible", () => !analysisContainer.AimLinesVisible); } private Replay fabricateReplay() diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs index 8a48e811118e..2b7f6c9fc96d 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysisOverlay.cs @@ -65,13 +65,11 @@ protected override void Update() base.Update(); if (requireDisplay) - { initialise(); - if (ClickMarkers != null) ClickMarkers.Alpha = showClickMarkers.Value ? 1 : 0; - if (FrameMarkers != null) FrameMarkers.Alpha = showFrameMarkers.Value ? 1 : 0; - if (CursorPath != null) CursorPath.Alpha = showCursorPath.Value ? 1 : 0; - } + if (ClickMarkers != null) ClickMarkers.Alpha = showClickMarkers.Value ? 1 : 0; + if (FrameMarkers != null) FrameMarkers.Alpha = showFrameMarkers.Value ? 1 : 0; + if (CursorPath != null) CursorPath.Alpha = showCursorPath.Value ? 1 : 0; } private void initialise() From 86a06c7e103a0a8a4584bdeb0229e5b1631e0869 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 17:19:53 +0900 Subject: [PATCH 528/579] Fix high performance session potentially getting stuck after multiplayer spectator --- osu.Game/Screens/Play/PlayerLoader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 12048ecbbe9e..7682bba9a619 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -573,6 +573,9 @@ protected override void Dispose(bool isDisposing) // if the player never got pushed, we should explicitly dispose it. DisposalTask = LoadTask?.ContinueWith(_ => CurrentPlayer?.Dispose()); } + + highPerformanceSession?.Dispose(); + highPerformanceSession = null; } #endregion From e1b763ff0db4de395f93171aa567cb41e7dd9171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Sep 2024 11:21:59 +0200 Subject: [PATCH 529/579] Apply review suggestions wrt border appearance --- osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 985eb74662c2..044576c63595 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -80,6 +80,7 @@ private void load(OsuColour colours) Masking = true; CornerRadius = 5; + CornerExponent = 2.5f; InternalChildren = new Drawable[] { @@ -178,7 +179,7 @@ private void updateState() if (!disabled) { - BorderThickness = IsHovered || textBox.Focused.Value ? 3 : 0; + BorderThickness = IsHovered || textBox.Focused.Value ? 2 : 0; BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4; if (textBox.Focused.Value) From 791ce218fcd4c8bff495f869d92d1d25a63d23bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 18:55:11 +0900 Subject: [PATCH 530/579] Add test coverage of beatmap offset edge case failure --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 3b88750013a8..c7f1eabab235 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -136,6 +137,59 @@ public void TestCalibrationFromNonZero() AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + [Test] + public void TestCalibrationFromNonZeroWithImmediateReferenceScore() + { + const double average_error = -4.5; + const double initial_offset = -2; + + AddStep("Set beatmap offset non-neutral", () => Realm.Write(r => + { + r.Add(new BeatmapInfo + { + ID = Beatmap.Value.BeatmapInfo.ID, + Ruleset = Beatmap.Value.BeatmapInfo.Ruleset, + UserSettings = + { + Offset = initial_offset, + } + }); + })); + + AddStep("Create control with preloaded reference score", () => + { + Child = new PlayerSettingsGroup("Some settings") + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + offsetControl = new BeatmapOffsetControl + { + ReferenceScore = + { + Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error), + BeatmapInfo = Beatmap.Value.BeatmapInfo, + } + } + } + } + }; + }); + + AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value, () => Is.EqualTo(initial_offset - average_error)); + + AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + + AddStep("Clean up beatmap", () => Realm.Write(r => r.RemoveAll())); + } + [Test] public void TestCalibrationNoChange() { From 37f61b26ea858b53e29aefc784b1563b8ed56c59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2024 18:45:49 +0900 Subject: [PATCH 531/579] Fix offset adjust control not correctly applying changes after song select quit This is an interesting scenario where we arrive at a fresh `BeatmapOffsetControl` but with a reference score (from the last play). Our best assumption here is that the beatmap's offset hasn't changed since the last play, so we want to use it for the `lastPlayBeatmapOffset`. But due to unfortunate order of execution, `Current.Value` was not yet initialised. --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 7668d3e63529..f312fb0ec5a9 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -104,8 +104,6 @@ protected override void LoadComplete() { base.LoadComplete(); - ReferenceScore.BindValueChanged(scoreChanged, true); - beatmapOffsetSubscription = realm.SubscribeToPropertyChanged( r => r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings, settings => settings.Offset, @@ -124,6 +122,7 @@ protected override void LoadComplete() }); Current.BindValueChanged(currentChanged); + ReferenceScore.BindValueChanged(scoreChanged, true); } private void currentChanged(ValueChangedEvent offset) From e94e08fec3448012c041a301c5d76f1ff0776ee6 Mon Sep 17 00:00:00 2001 From: Sheppsu <49356627+Sheppsu@users.noreply.github.com> Date: Thu, 5 Sep 2024 20:14:36 -0400 Subject: [PATCH 532/579] fix marker depth when rewinding --- osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs index 9b602c88a854..187876d69163 100644 --- a/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs +++ b/osu.Game.Rulesets.Osu/UI/ReplayAnalysis/AnalysisMarker.cs @@ -22,6 +22,7 @@ private void load() protected override void OnApply(AnalysisFrameEntry entry) { Position = entry.Position; + Depth = -(float)entry.LifetimeEnd; } } } From 36a30cf0772d67743032b168886b1f05fd08bc36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2024 16:01:47 +0900 Subject: [PATCH 533/579] Add note about using hard links in the future --- osu.Game/Database/RealmArchiveModelImporter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 38df2ac1dcf6..cf0625c51c99 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -195,6 +195,7 @@ public async Task> BeginExternalEditing(TModel mod Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!); + // Consider using hard links here to make this instant. using (var inStream = Files.Storage.GetStream(sourcePath)) using (var outStream = File.Create(destinationPath)) await inStream.CopyToAsync(outStream).ConfigureAwait(false); From 9f834ca1a2db21eea77dd687e8dbdef1ad7932eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 Sep 2024 10:24:05 +0200 Subject: [PATCH 534/579] Silence beatmap retrieval failures from results screen favourite button As reported (very poorly) in https://github.com/ppy/osu/pull/28991#issuecomment-2331854970. I believe this is a total edge case and is mostly visible on dev due to some beatmaps existing on `osu.ppy.sh` and not on `dev.ppy.sh`, but I tend to agree in general that these types of failures should not be firing very loud error notifications; logging to network and disabling the button with a tooltip adjustment should be enough. --- osu.Game/Screens/Ranking/FavouriteButton.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs index aecaf7c5b92f..019b80dde980 100644 --- a/osu.Game/Screens/Ranking/FavouriteButton.cs +++ b/osu.Game/Screens/Ranking/FavouriteButton.cs @@ -76,12 +76,13 @@ private void getBeatmapSet() }; beatmapSetRequest.Failure += e => { - Logger.Error(e, $"Failed to fetch beatmap info: {e.Message}"); + Logger.Log($"Favourite button failed to fetch beatmap info: {e}", LoggingTarget.Network); Schedule(() => { loading.Hide(); Enabled.Value = false; + TooltipText = "this beatmap cannot be favourited"; }); }; api.Queue(beatmapSetRequest); From 6913d75792585bab7f0c649dd6b5687e05753d33 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Sep 2024 18:04:11 +0900 Subject: [PATCH 535/579] Add 'yes'/'no' acronyms to the `played=` filter --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 4 ++++ osu.Game/Screens/Select/FilterQueryParser.cs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index e6006b7fd239..9ecfa72947d8 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -633,11 +633,15 @@ public void TestOutOfRangeDateQuery() new object[] { "0", DateTimeOffset.Now, false }, new object[] { "false", DateTimeOffset.MinValue, true }, new object[] { "false", DateTimeOffset.Now, false }, + new object[] { "no", DateTimeOffset.MinValue, true }, + new object[] { "no", DateTimeOffset.Now, false }, new object[] { "1", DateTimeOffset.MinValue, false }, new object[] { "1", DateTimeOffset.Now, true }, new object[] { "true", DateTimeOffset.MinValue, false }, new object[] { "true", DateTimeOffset.Now, true }, + new object[] { "yes", DateTimeOffset.MinValue, false }, + new object[] { "yes", DateTimeOffset.Now, true }, }; [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 3e0dba59f09b..6c9a95a250fb 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -159,10 +159,12 @@ private static bool tryParseBool(string value, out bool result) switch (value) { case "1": + case "yes": result = true; return true; case "0": + case "no": result = false; return true; From 15f73a3dfb3dcc838809dd48813c40cfd8d6a784 Mon Sep 17 00:00:00 2001 From: Michael Bui Date: Fri, 6 Sep 2024 21:52:42 +1200 Subject: [PATCH 536/579] show participation count in tooltip --- .../Header/Components/DailyChallengeStatsTooltip.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index 64a8d67c5bcf..5d89406c344f 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -26,6 +26,7 @@ public partial class DailyChallengeStatsTooltip : VisibilityContainer, ITooltip< { private StreakPiece currentDaily = null!; private StreakPiece currentWeekly = null!; + private StreakPiece totalParticipation = null!; private StatisticsPiece bestDaily = null!; private StatisticsPiece bestWeekly = null!; private StatisticsPiece topTen = null!; @@ -70,7 +71,7 @@ private void load() { topBackground = new Box { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.None, }, new FillFlowContainer { @@ -78,8 +79,9 @@ private void load() Direction = FillDirection.Horizontal, Padding = new MarginPadding(15f), Spacing = new Vector2(30f), - Children = new[] + Children = new Drawable[] { + totalParticipation = new StreakPiece(UsersStrings.ShowDailyChallengePlaycount), currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent), currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent), } @@ -113,6 +115,9 @@ public void SetContent(DailyChallengeTooltipData content) background.Colour = colourProvider.Background4; topBackground.Colour = colourProvider.Background5; + totalParticipation.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.PlayCount.ToLocalisableString(@"N0")); + totalParticipation.ValueColour = colourProvider.Content2; + currentDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); From f59895aa3444eca3a167d2c3b315bb054626d091 Mon Sep 17 00:00:00 2001 From: Michael Bui Date: Fri, 6 Sep 2024 21:54:41 +1200 Subject: [PATCH 537/579] take out drawable --- .../Profile/Header/Components/DailyChallengeStatsTooltip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index 5d89406c344f..df52fea15899 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -79,7 +79,7 @@ private void load() Direction = FillDirection.Horizontal, Padding = new MarginPadding(15f), Spacing = new Vector2(30f), - Children = new Drawable[] + Children = new[] { totalParticipation = new StreakPiece(UsersStrings.ShowDailyChallengePlaycount), currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent), From 34a9d60c190c2caf0a20c35508f8e592af6c414f Mon Sep 17 00:00:00 2001 From: Michael Bui Date: Fri, 6 Sep 2024 22:02:35 +1200 Subject: [PATCH 538/579] revert back to axes.both --- .../Profile/Header/Components/DailyChallengeStatsTooltip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index df52fea15899..93ec3b941a82 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -71,7 +71,7 @@ private void load() { topBackground = new Box { - RelativeSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, }, new FillFlowContainer { From ab8771900a44a2a3f01fd5494091866f3ebe7443 Mon Sep 17 00:00:00 2001 From: Michael Bui Date: Fri, 6 Sep 2024 22:04:10 +1200 Subject: [PATCH 539/579] change colour --- .../Profile/Header/Components/DailyChallengeStatsTooltip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index 93ec3b941a82..bc389c5569c8 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -116,7 +116,7 @@ public void SetContent(DailyChallengeTooltipData content) topBackground.Colour = colourProvider.Background5; totalParticipation.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.PlayCount.ToLocalisableString(@"N0")); - totalParticipation.ValueColour = colourProvider.Content2; + totalParticipation.ValueColour = colours.ForRankingTier(TierForDaily(statistics.PlayCount)); currentDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); From 362b4bbc566ee0a32391d81f4936ef16a9b8744f Mon Sep 17 00:00:00 2001 From: Michael Bui Date: Fri, 6 Sep 2024 23:01:05 +1200 Subject: [PATCH 540/579] Hide daily challenge stats if there are no plays --- .../Profile/Header/Components/DailyChallengeStatsDisplay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index 41fd2be591a9..80487b19c69b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -107,6 +107,12 @@ private void updateDisplay() APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics; + if (stats.PlayCount == 0) + { + Hide(); + return; + } + dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0")); dailyPlayCount.Colour = colours.ForRankingTier(TierForPlayCount(stats.PlayCount)); From a31ea24c6d2ebe7937e2148f6ba754cace417e9d Mon Sep 17 00:00:00 2001 From: Michael Bui Date: Fri, 6 Sep 2024 23:05:04 +1200 Subject: [PATCH 541/579] show stats on all rulesets --- .../Profile/Header/Components/DailyChallengeStatsDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index 80487b19c69b..cdc460e1a829 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -99,7 +99,7 @@ protected override void LoadComplete() private void updateDisplay() { - if (User.Value == null || User.Value.Ruleset.OnlineID != 0) + if (User.Value == null) { Hide(); return; From 7e53df5226667d6b7c1ba13bc9898a066e722ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 Sep 2024 13:01:50 +0200 Subject: [PATCH 542/579] Add failing test coverage --- .../TestScenePlayerScoreSubmission.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 5e22e4757241..c382f0828b15 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -8,7 +8,9 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -26,6 +28,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -177,6 +180,30 @@ public void TestNoSubmissionOnEmptyFail() AddAssert("ensure no submission", () => Player.SubmittedScore == null); } + [Test] + public void TestEmptyFailStillImports() + { + prepareTestAPI(true); + + createPlayerTest(true); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value, () => Is.EqualTo(Visibility.Visible)); + + AddStep("attempt import", () => + { + InputManager.MoveMouseTo(Player.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("wait for import to start", () => Player.ScoreImportStarted); + AddStep("allow import", () => Player.AllowImportCompletion.Release()); + + AddUntilStep("import completed", () => Player.ImportedScore, () => Is.Not.Null); + AddAssert("ensure no submission", () => Player.SubmittedScore, () => Is.Null); + } + [Test] public void TestSubmissionOnFail() { @@ -378,6 +405,8 @@ protected partial class FakeImportingPlayer : TestPlayer public SemaphoreSlim AllowImportCompletion { get; } public Score ImportedScore { get; private set; } + public new FailOverlay FailOverlay => base.FailOverlay; + public FakeImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) : base(allowPause, showResults, pauseOnFocusLost) { From 4e9ad1388fb0de72c6197faa9ad6d56fb1a87087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 Sep 2024 13:16:27 +0200 Subject: [PATCH 543/579] Fix stall when attempting to import replay after hitting nothing --- osu.Game/Screens/Play/SubmittingPlayer.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 6c5f7fab9e82..aea3bf6d5cf8 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -274,6 +274,16 @@ private Task submitScore(Score score) return Task.CompletedTask; } + // if the user never hit anything, this score should not be counted in any way. + if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0)) + { + Logger.Log("No hits registered, skipping score submission"); + return Task.CompletedTask; + } + + // mind the timing of this. + // once `scoreSubmissionSource` is created, it is presumed that submission is taking place in the background, + // so all exceptional circumstances that would disallow submission must be handled above. lock (scoreSubmissionLock) { if (scoreSubmissionSource != null) @@ -282,10 +292,6 @@ private Task submitScore(Score score) scoreSubmissionSource = new TaskCompletionSource(); } - // if the user never hit anything, this score should not be counted in any way. - if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0)) - return Task.CompletedTask; - Logger.Log($"Beginning score submission (token:{token.Value})..."); var request = CreateSubmissionRequest(score, token.Value); From 575da0992fa2f07792368b294cb1430684ea2a44 Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Fri, 6 Sep 2024 16:16:40 -0400 Subject: [PATCH 544/579] Fix file associations not updating & uninstalling --- osu.Desktop/Program.cs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 510366381501..5100eef3d9a1 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -168,12 +168,30 @@ private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args) private static void setupVelopack() { - VelopackApp - .Build() - .WithFirstRun(v => + var app = VelopackApp.Build(); + + app.WithFirstRun(_ => + { + if (OperatingSystem.IsWindows()) + WindowsAssociationManager.InstallAssociations(); + }); + + if (OperatingSystem.IsWindows()) + { + app.WithAfterUpdateFastCallback(_ => { - if (OperatingSystem.IsWindows()) WindowsAssociationManager.InstallAssociations(); - }).Run(); + if (OperatingSystem.IsWindows()) + WindowsAssociationManager.UpdateAssociations(); + }); + + app.WithBeforeUninstallFastCallback(_ => + { + if (OperatingSystem.IsWindows()) + WindowsAssociationManager.UninstallAssociations(); + }); + } + + app.Run(); } } } From ed044d5b85d80b1cdf03555e0be1530ffd166626 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Fri, 6 Sep 2024 22:58:18 +0200 Subject: [PATCH 545/579] Fix proposal for #29736 --- osu.Game/Screens/Ranking/CollectionPopover.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/CollectionPopover.cs b/osu.Game/Screens/Ranking/CollectionPopover.cs index 6617ac334f7a..ffc448d7a92c 100644 --- a/osu.Game/Screens/Ranking/CollectionPopover.cs +++ b/osu.Game/Screens/Ranking/CollectionPopover.cs @@ -39,6 +39,7 @@ private void load() new OsuMenu(Direction.Vertical, true) { Items = items, + MaxHeight = 375, }, }; } From 581f190856274ad1c521fb8ae32a503ebeed78ef Mon Sep 17 00:00:00 2001 From: Ianlucht Date: Fri, 6 Sep 2024 16:31:48 -0600 Subject: [PATCH 546/579] fixed issues with search by adding the double quotation marks in the BeatmapSetHeaderContents links. --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 168056ea58d1..d9747d1f4464 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -242,7 +242,7 @@ private void load(OverlayColourProvider colourProvider) title.Clear(); artist.Clear(); - title.AddLink(titleText, LinkAction.SearchBeatmapSet, titleText); + title.AddLink(titleText, LinkAction.SearchBeatmapSet, "title=\"\"" + titleText + "\"\""); title.AddArbitraryDrawable(Empty().With(d => d.Width = 5)); title.AddArbitraryDrawable(externalLink = new ExternalLinkButton()); @@ -259,7 +259,7 @@ private void load(OverlayColourProvider colourProvider) title.AddArbitraryDrawable(new SpotlightBeatmapBadge()); } - artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); + artist.AddLink(artistText, LinkAction.SearchBeatmapSet, "artist=\"\"" + artistText + "\"\""); if (setInfo.NewValue.TrackId != null) { From 3b81ad4cbffe88ff0ca16a0f26e74fc3c30b7c5b Mon Sep 17 00:00:00 2001 From: Bruno Heredia <111712756+Bruno5430@users.noreply.github.com> Date: Sat, 7 Sep 2024 01:42:47 -0300 Subject: [PATCH 547/579] Fix scroll speed slider defaulting to 0.01 --- osu.Game/Screens/Edit/Timing/EffectSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index a4b9f37dffd6..f9ef46023266 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -56,7 +56,7 @@ protected override void OnControlPointChanged(ValueChangedEvent Date: Sat, 7 Sep 2024 13:54:12 +0900 Subject: [PATCH 548/579] Fix display length not resetting to default because default was wrong Closes https://github.com/ppy/osu/issues/29757. --- osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index 8a8b78b64509..580c7e6bd88f 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -27,7 +27,7 @@ protected override void InitialiseDefaults() SetDefault(OsuRulesetSetting.ReplayFrameMarkersEnabled, false); SetDefault(OsuRulesetSetting.ReplayCursorPathEnabled, false); SetDefault(OsuRulesetSetting.ReplayCursorHideEnabled, false); - SetDefault(OsuRulesetSetting.ReplayAnalysisDisplayLength, 750); + SetDefault(OsuRulesetSetting.ReplayAnalysisDisplayLength, 800); } } From 9b189fd244f10613f616b077d1f83b6e1396cf85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 Sep 2024 13:58:17 +0900 Subject: [PATCH 549/579] Fix windows check weirdness --- osu.Desktop/Program.cs | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 5100eef3d9a1..e78c2ca636ef 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Runtime.Versioning; using osu.Desktop.LegacyIpc; using osu.Desktop.Windows; using osu.Framework; @@ -170,28 +171,18 @@ private static void setupVelopack() { var app = VelopackApp.Build(); - app.WithFirstRun(_ => - { - if (OperatingSystem.IsWindows()) - WindowsAssociationManager.InstallAssociations(); - }); - if (OperatingSystem.IsWindows()) - { - app.WithAfterUpdateFastCallback(_ => - { - if (OperatingSystem.IsWindows()) - WindowsAssociationManager.UpdateAssociations(); - }); - - app.WithBeforeUninstallFastCallback(_ => - { - if (OperatingSystem.IsWindows()) - WindowsAssociationManager.UninstallAssociations(); - }); - } + configureWindows(app); app.Run(); } + + [SupportedOSPlatform("windows")] + private static void configureWindows(VelopackApp app) + { + app.WithFirstRun(_ => WindowsAssociationManager.InstallAssociations()); + app.WithAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations()); + app.WithBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations()); + } } } From ac6cce5911d78a4321b679c1d93213954865a5a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 7 Sep 2024 17:40:33 +0900 Subject: [PATCH 550/579] Refactor to string interpolation --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index d9747d1f4464..f9e0c6c380ef 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -242,7 +242,7 @@ private void load(OverlayColourProvider colourProvider) title.Clear(); artist.Clear(); - title.AddLink(titleText, LinkAction.SearchBeatmapSet, "title=\"\"" + titleText + "\"\""); + title.AddLink(titleText, LinkAction.SearchBeatmapSet, $@"title=""""{titleText}"""""); title.AddArbitraryDrawable(Empty().With(d => d.Width = 5)); title.AddArbitraryDrawable(externalLink = new ExternalLinkButton()); @@ -259,7 +259,7 @@ private void load(OverlayColourProvider colourProvider) title.AddArbitraryDrawable(new SpotlightBeatmapBadge()); } - artist.AddLink(artistText, LinkAction.SearchBeatmapSet, "artist=\"\"" + artistText + "\"\""); + artist.AddLink(artistText, LinkAction.SearchBeatmapSet, $@"artist=""""{artistText}"""""); if (setInfo.NewValue.TrackId != null) { From 10ef5a6d6dec110525cf59f7c9e14a0d11709c37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 Sep 2024 21:46:43 +0900 Subject: [PATCH 551/579] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5f3dd2f6f4ab..7b45b9dec455 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 9d9b42a16378..1d76deddaced 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 3e3ee3757c7688046b77fe4ee947d8ebf7e68dbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 Sep 2024 22:13:54 +0900 Subject: [PATCH 552/579] Add failing test case for difficulty splitting --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index ec072a3dd2d5..fbed577ed243 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -520,6 +520,18 @@ public void TestAddRemove() waitForSelection(set_count); } + [Solo] + [Test] + public void TestDifficultiesSplitOutOnLoad() + { + loadBeatmaps(new List { TestResources.CreateTestBeatmapSetInfo(diff_count) }, () => new FilterCriteria + { + Sort = SortMode.Difficulty, + }); + + checkVisibleItemCount(false, 3); + } + [Test] public void TestAddRemoveDifficultySort() { From 4c6eb895309c33653bf0e2798ec41920e3567c7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 Sep 2024 22:05:33 +0900 Subject: [PATCH 553/579] Fix beatmap difficulties not being split out on first load Closes https://github.com/ppy/osu/issues/29728. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a6a6a2f58528..2486b26f258b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -137,6 +137,8 @@ internal IEnumerable BeatmapSets private void loadNewRoot() { + beatmapsSplitOut = activeCriteria.SplitOutDifficulties; + // Ensure no changes are made to the list while we are initialising items. // We'll catch up on changes via subscriptions anyway. BeatmapSetInfo[] loadableSets = detachedBeatmapSets!.ToArray(); @@ -726,7 +728,6 @@ void perform() if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut) { - beatmapsSplitOut = activeCriteria.SplitOutDifficulties; loadNewRoot(); return; } From 32de8e9b2da88e2edbcb06ae8434c15a342dc3fe Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 7 Sep 2024 16:15:00 +0200 Subject: [PATCH 554/579] Fixed ControlPointTable items being blocked by buttons --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 501d8c0e4150..c0b9ccb2becc 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -53,6 +53,7 @@ public partial class ControlPointTable : CompositeDrawable private void load(OverlayColourProvider colours) { RelativeSizeAxes = Axes.Both; + Padding = new() { Bottom = 50 }; InternalChildren = new Drawable[] { From 2bc6547d49e3578c8bbb5590dafcaf93781eccf5 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 7 Sep 2024 16:23:23 +0200 Subject: [PATCH 555/579] Code quality fix: added type --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index c0b9ccb2becc..dd0cf2116e7e 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -53,7 +53,7 @@ public partial class ControlPointTable : CompositeDrawable private void load(OverlayColourProvider colours) { RelativeSizeAxes = Axes.Both; - Padding = new() { Bottom = 50 }; + Padding = new MarginPadding { Bottom = 50 }; InternalChildren = new Drawable[] { From 958bfde51d49f526d928a5976a6c0c4f21250bbb Mon Sep 17 00:00:00 2001 From: Ianlucht <90893791+Ianlucht@users.noreply.github.com> Date: Sat, 7 Sep 2024 13:46:42 -0600 Subject: [PATCH 556/579] added DailyChallengeIntro to notification --- .../DailyChallenge/NewDailyChallengeNotification.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs index ea19828a2106..e305de0aaf2d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -5,12 +5,14 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Overlays.Notifications; using osu.Game.Screens.Menu; using osu.Game.Localisation; + namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class NewDailyChallengeNotification : SimpleNotification @@ -24,14 +26,18 @@ public NewDailyChallengeNotification(Room room) this.room = room; } + [BackgroundDependencyLoader] - private void load(OsuGame? game) + private void load(OsuGame? game, SessionStatics statics) { Text = DailyChallengeStrings.ChallengeLiveNotification; Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!)); Activated = () => { - game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]); + if(statics.Get(Static.DailyChallengeIntroPlayed)) + game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]); + else + game?.PerformFromScreen(s => s.Push(new DailyChallengeIntro(room)), [typeof(MainMenu)]); return true; }; } From 170737b68f76d9269a81b7c91125c7518933f0b2 Mon Sep 17 00:00:00 2001 From: Ianlucht <90893791+Ianlucht@users.noreply.github.com> Date: Sat, 7 Sep 2024 13:48:14 -0600 Subject: [PATCH 557/579] added DailyChallengeIntro to notification --- .../OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs index e305de0aaf2d..8e4337274f2f 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -12,7 +12,6 @@ using osu.Game.Screens.Menu; using osu.Game.Localisation; - namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class NewDailyChallengeNotification : SimpleNotification @@ -38,6 +37,7 @@ private void load(OsuGame? game, SessionStatics statics) game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]); else game?.PerformFromScreen(s => s.Push(new DailyChallengeIntro(room)), [typeof(MainMenu)]); + return true; }; } From e6f81abc3bcaaa82540931d30ed42485165231e8 Mon Sep 17 00:00:00 2001 From: Ianlucht <90893791+Ianlucht@users.noreply.github.com> Date: Sat, 7 Sep 2024 13:57:12 -0600 Subject: [PATCH 558/579] cleaned up whitespace --- .../OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs index 8e4337274f2f..35191f4ffa9f 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -25,7 +25,6 @@ public NewDailyChallengeNotification(Room room) this.room = room; } - [BackgroundDependencyLoader] private void load(OsuGame? game, SessionStatics statics) { From cd94d6e2bcad42b630930621fa95bb3c54761e40 Mon Sep 17 00:00:00 2001 From: Ianlucht <90893791+Ianlucht@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:01:38 -0600 Subject: [PATCH 559/579] fixed if statement format --- .../OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs index 35191f4ffa9f..7ae6992bec75 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -32,7 +32,7 @@ private void load(OsuGame? game, SessionStatics statics) Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!)); Activated = () => { - if(statics.Get(Static.DailyChallengeIntroPlayed)) + if (statics.Get(Static.DailyChallengeIntroPlayed)) game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]); else game?.PerformFromScreen(s => s.Push(new DailyChallengeIntro(room)), [typeof(MainMenu)]); From 4cf057db8f62b82ffac6d954670e3bcbe0711e72 Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Sun, 8 Sep 2024 01:13:48 -0400 Subject: [PATCH 560/579] Completely disable velopack when using external update manager --- osu.Desktop/Program.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 510366381501..117ba66784ed 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -168,6 +168,14 @@ private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args) private static void setupVelopack() { + string? packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER"); + + if (!string.IsNullOrEmpty(packageManaged)) + { + Logger.Log("Updates are being managed by an external provider. Skipping Velopack setup"); + return; + } + VelopackApp .Build() .WithFirstRun(v => From 7f814d3106b67158c16a336de7e01910dee4ba7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 Sep 2024 14:16:37 +0200 Subject: [PATCH 561/579] Fix incorrect tiers being used for tooltip total participation display Compare: https://github.com/ppy/osu-web/pull/11457/commits/95e4561a54353016f25c3fc859b176038b82088a --- .../Online/TestSceneUserProfileDailyChallenge.cs | 4 ++-- .../Header/Components/DailyChallengeStatsDisplay.cs | 9 +-------- .../Header/Components/DailyChallengeStatsTooltip.cs | 11 +++++++++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index d7f5f6576940..9db30380f650 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -66,8 +66,8 @@ private void update(Action change) [Test] public void TestPlayCountRankingTier() { - AddAssert("1 before silver", () => DailyChallengeStatsDisplay.TierForPlayCount(30) == RankingTier.Bronze); - AddAssert("first silver", () => DailyChallengeStatsDisplay.TierForPlayCount(31) == RankingTier.Silver); + AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Bronze); + AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(31) == RankingTier.Silver); } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index cdc460e1a829..3e86b2268f9c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; @@ -14,7 +13,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Header.Components { @@ -114,18 +112,13 @@ private void updateDisplay() } dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0")); - dailyPlayCount.Colour = colours.ForRankingTier(TierForPlayCount(stats.PlayCount)); + dailyPlayCount.Colour = colours.ForRankingTier(DailyChallengeStatsTooltip.TierForPlayCount(stats.PlayCount)); TooltipContent = new DailyChallengeTooltipData(colourProvider, stats); Show(); } - // Rounding up is needed here to ensure the overlay shows the same colour as osu-web for the play count. - // This is because, for example, 31 / 3 > 10 in JavaScript because floats are used, while here it would - // get truncated to 10 with an integer division and show a lower tier. - public static RankingTier TierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily((int)Math.Ceiling(playCount / 3.0d)); - public ITooltip GetCustomTooltip() => new DailyChallengeStatsTooltip(); } } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index bc389c5569c8..24e531bd87a1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.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.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; @@ -116,7 +117,7 @@ public void SetContent(DailyChallengeTooltipData content) topBackground.Colour = colourProvider.Background5; totalParticipation.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.PlayCount.ToLocalisableString(@"N0")); - totalParticipation.ValueColour = colours.ForRankingTier(TierForDaily(statistics.PlayCount)); + totalParticipation.ValueColour = colours.ForRankingTier(TierForPlayCount(statistics.PlayCount)); currentDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); @@ -137,7 +138,13 @@ public void SetContent(DailyChallengeTooltipData content) topFifty.ValueColour = colourProvider.Content2; } - // reference: https://github.com/ppy/osu-web/blob/8206e0e91eeea80ccf92f0586561346dd40e085e/resources/js/profile-page/daily-challenge.tsx#L13-L43 + // reference: https://github.com/ppy/osu-web/blob/adf1e94754ba9625b85eba795f4a310caf169eec/resources/js/profile-page/daily-challenge.tsx#L13-L47 + + // Rounding up is needed here to ensure the overlay shows the same colour as osu-web for the play count. + // This is because, for example, 31 / 3 > 10 in JavaScript because floats are used, while here it would + // get truncated to 10 with an integer division and show a lower tier. + public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Ceiling(playCount / 3.0d)); + public static RankingTier TierForDaily(int daily) { if (daily > 360) From cf23c6668c3281e5644721665e68ec1265e26868 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sun, 8 Sep 2024 15:59:23 +0200 Subject: [PATCH 562/579] Added background color to hide beatmap background --- .../Screens/Edit/Timing/ControlPointList.cs | 88 +++++++++++-------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 8699c388b383..6a21ff0053fa 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -7,11 +7,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Timing @@ -30,6 +32,9 @@ public partial class ControlPointList : CompositeDrawable [Resolved] private Bindable selectedGroup { get; set; } = null!; + [Cached] + private OverlayColourProvider overlayColourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -43,51 +48,62 @@ private void load(OsuColour colours) RelativeSizeAxes = Axes.Both, Groups = { BindTarget = Beatmap.ControlPointInfo.Groups, }, }, - new FillFlowContainer + new Container { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding(margins), - Spacing = new Vector2(5), + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, Children = new Drawable[] { - new RoundedButton + new Box { - Text = "Select closest to current time", - Action = goToCurrentGroup, - Size = new Vector2(220, 30), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, + Height = 50, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Colour = overlayColourProvider.Background2, }, - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding(margins), - Spacing = new Vector2(5), - Children = new Drawable[] - { - deleteButton = new RoundedButton + new FillFlowContainer { - Text = "-", - Size = new Vector2(30, 30), - Action = delete, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - BackgroundColour = colours.Red3, + Anchor = Anchor.BottomLeft, + Padding = new MarginPadding { Left = margins, Bottom = margins }, + Children = new Drawable[] + { + new RoundedButton + { + Text = "Select closest to current time", + Action = goToCurrentGroup, + Size = new Vector2(220, 30), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + } }, - addButton = new RoundedButton + new FillFlowContainer { - Action = addNew, - Size = new Vector2(160, 30), + Direction = FillDirection.Horizontal, Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, + Spacing = new Vector2(5), + Padding = new MarginPadding { Right = margins, Bottom = margins }, + Children = new Drawable[] + { + deleteButton = new RoundedButton + { + Text = "-", + Size = new Vector2(30, 30), + Action = delete, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + BackgroundColour = colours.Red3, + }, + addButton = new RoundedButton + { + Action = addNew, + Size = new Vector2(160, 30), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } }, } }, From 2e6f17f25399684681c22f5701717037808c97aa Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sun, 8 Sep 2024 16:04:10 +0200 Subject: [PATCH 563/579] Fixed wrong OverlayColourScheme --- osu.Game/Screens/Edit/Timing/ControlPointList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 6a21ff0053fa..03ad1a631a8a 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -33,7 +33,7 @@ public partial class ControlPointList : CompositeDrawable private Bindable selectedGroup { get; set; } = null!; [Cached] - private OverlayColourProvider overlayColourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + private OverlayColourProvider overlayColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); [BackgroundDependencyLoader] private void load(OsuColour colours) From 134bcc85b76748fc5e5b4678f53498a853bd7a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 Sep 2024 14:53:43 +0200 Subject: [PATCH 564/579] Add failing test case --- .../SongSelect/TestSceneBeatmapCarousel.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index fbed577ed243..97c46a11fc58 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -520,7 +520,6 @@ public void TestAddRemove() waitForSelection(set_count); } - [Solo] [Test] public void TestDifficultiesSplitOutOnLoad() { @@ -1132,6 +1131,32 @@ public void TestCarouselRemembersSelectionDifficultySort() AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1); } + [Test] + public void TestCarouselRetainsSelectionFromDifficultySort() + { + List manySets = new List(); + + AddStep("Populate beatmap sets", () => + { + manySets.Clear(); + + for (int i = 1; i <= 50; i++) + manySets.Add(TestResources.CreateTestBeatmapSetInfo(diff_count)); + }); + + loadBeatmaps(manySets); + + BeatmapInfo chosenBeatmap = null!; + AddStep("select given beatmap", () => carousel.SelectBeatmap(chosenBeatmap = manySets[20].Beatmaps[0])); + AddUntilStep("selection changed", () => carousel.SelectedBeatmapInfo, () => Is.EqualTo(chosenBeatmap)); + + AddStep("sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty })); + AddAssert("selection retained", () => carousel.SelectedBeatmapInfo, () => Is.EqualTo(chosenBeatmap)); + + AddStep("sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title })); + AddAssert("selection retained", () => carousel.SelectedBeatmapInfo, () => Is.EqualTo(chosenBeatmap)); + } + [Test] public void TestFilteringByUserStarDifficulty() { From cefbc76490d5b17f5607fd579b86f3c0b89104fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 Sep 2024 15:51:59 +0200 Subject: [PATCH 565/579] Fix selection being dropped when changing carousel sort mode from difficulty sort Closes https://github.com/ppy/osu/issues/29738. This "regressed" in https://github.com/ppy/osu/pull/29639, but if I didn't know better, I'd go as far as saying that this looks like a .NET bug, because the fact that PR broke it looks not sane. The TL;DR on this is that before the pull in question, the offending `.Contains()` check that this commit modifies was called on a `List`. The pull changed the collection type to `BeatmapSetInfo[]`. That said, the call is a LINQ call, so all good, right? Not really. First off, the default overload resolution order means that the previous code would call `List.Contains()`, and not `Enumerable.Contains()`. Then again, why would that matter? In both cases `T` is still `BeatmapSetInfo`, right? Well... about that... It is difficult to tell for sure what precisely is happening here, because of what looks like runtime magic. The end *symptom* is that the old code ended up calling `Array.IndexOf()`, and the new code ends up calling... `Array.IndexOf()`. So while yes, `BeatmapSetInfo` implements `IEquatable` and the expectation would be that `Equals()` should be getting called, the type elision to `object` means that we're back to reference equality semantics, because that's what `EqualityComparer.Default` is. A five-minute github search across dotnet/runtime yields this: https://github.com/dotnet/runtime/blob/c4792a228ea36792b90f87ddf7fce2477e827822/src/coreclr/vm/array.cpp#L984-L990 Now again, if I didn't know better, I'd see that "OPTIMIZATION:" comment, see what transpired in this scenario, and call that optimisation invalid, because it changes semantics. But I *probably* know that the dotnet team knows better and am probably just going to take it for what it is, because blame on that code looks to be years old and it's probably not a new behaviour. (I haven't tested empirically if it is.) Instead the fix is just to tell the `.Contains()` method to use the correct comparer. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2486b26f258b..525884c413e2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -143,7 +143,7 @@ private void loadNewRoot() // We'll catch up on changes via subscriptions anyway. BeatmapSetInfo[] loadableSets = detachedBeatmapSets!.ToArray(); - if (selectedBeatmapSet != null && !loadableSets.Contains(selectedBeatmapSet.BeatmapSet)) + if (selectedBeatmapSet != null && !loadableSets.Contains(selectedBeatmapSet.BeatmapSet, EqualityComparer.Default)) selectedBeatmapSet = null; var selectedBeatmapBefore = selectedBeatmap?.BeatmapInfo; From 10e84d72e566e0f9188985ac1ae1adfd03865e22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 Sep 2024 23:07:17 +0900 Subject: [PATCH 566/579] Fix restart notifications appearing every 30 minutes If a user was to manually check for updates via the button, the recheck would have been fired. This is a recent regression. I kinda want to reorganise this code (the button press for check for udpates shouldn't even get close to the recheck code IMO) but for now this seems like one we should quickly fix. Addresses https://github.com/ppy/osu/discussions/29774. --- osu.Desktop/Updater/VelopackUpdateManager.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index e550755fffea..c2965428f7ea 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -45,14 +45,17 @@ private void load(INotificationOverlay notifications) private async Task checkForUpdateAsync(UpdateProgressNotification? notification = null) { - // should we schedule a retry on completion of this check? - bool scheduleRecheck = true; + // whether to check again in 30 minutes. generally only if there's an error or no update was found (yet). + bool scheduleRecheck = false; try { // Avoid any kind of update checking while gameplay is running. if (localUserInfo?.IsPlaying.Value == true) + { + scheduleRecheck = true; return false; + } // TODO: we should probably be checking if there's a more recent update, rather than shortcutting here. // Velopack does support this scenario (see https://github.com/ppy/osu/pull/28743#discussion_r1743495975). @@ -67,17 +70,20 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific return true; } }); + return true; } pendingUpdate = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false); - // Handle no updates available. + // No update is available. We'll check again later. if (pendingUpdate == null) + { + scheduleRecheck = true; return false; + } - scheduleRecheck = false; - + // An update is found, let's notify the user and start downloading it. if (notification == null) { notification = new UpdateProgressNotification @@ -113,7 +119,6 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific { if (scheduleRecheck) { - // check again in 30 minutes. Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30); } } From f5c5614eef02feb0c816f31ce1d4e9dae163ecdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 Sep 2024 16:29:53 +0200 Subject: [PATCH 567/579] Resolve existing colour provider instead of re-caching own one --- osu.Game/Screens/Edit/Timing/ControlPointList.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 03ad1a631a8a..4cc356012fb5 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -32,11 +32,8 @@ public partial class ControlPointList : CompositeDrawable [Resolved] private Bindable selectedGroup { get; set; } = null!; - [Cached] - private OverlayColourProvider overlayColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { RelativeSizeAxes = Axes.Both; From 7ec2e0e86696eb63b6b1e4995af2263d91d214f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 Sep 2024 16:30:09 +0200 Subject: [PATCH 568/579] Refactor layout code to be a bit less haphazard Visually the same, functionally much saner. --- .../Screens/Edit/Timing/ControlPointList.cs | 38 +++++++++++-------- .../Screens/Edit/Timing/ControlPointTable.cs | 7 +++- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 4cc356012fb5..49e5b76dd689 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit.Timing { public partial class ControlPointList : CompositeDrawable { + private ControlPointTable table = null!; + private Container controls = null!; private OsuButton deleteButton = null!; private RoundedButton addButton = null!; @@ -40,12 +42,12 @@ private void load(OsuColour colours, OverlayColourProvider colourProvider) const float margins = 10; InternalChildren = new Drawable[] { - new ControlPointTable + table = new ControlPointTable { RelativeSizeAxes = Axes.Both, Groups = { BindTarget = Beatmap.ControlPointInfo.Groups, }, }, - new Container + controls = new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -55,15 +57,16 @@ private void load(OsuColour colours, OverlayColourProvider colourProvider) { new Box { - Height = 50, - RelativeSizeAxes = Axes.X, - Anchor = Anchor.CentreLeft, - Colour = overlayColourProvider.Background2, + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, }, new FillFlowContainer { - Anchor = Anchor.BottomLeft, - Padding = new MarginPadding { Left = margins, Bottom = margins }, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding { Left = margins, Vertical = margins, }, Children = new Drawable[] { new RoundedButton @@ -71,17 +74,19 @@ private void load(OsuColour colours, OverlayColourProvider colourProvider) Text = "Select closest to current time", Action = goToCurrentGroup, Size = new Vector2(220, 30), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, } }, new FillFlowContainer { + AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomRight, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Spacing = new Vector2(5), - Padding = new MarginPadding { Right = margins, Bottom = margins }, + Padding = new MarginPadding { Right = margins, Vertical = margins, }, Children = new Drawable[] { deleteButton = new RoundedButton @@ -89,16 +94,16 @@ private void load(OsuColour colours, OverlayColourProvider colourProvider) Text = "-", Size = new Vector2(30, 30), Action = delete, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, BackgroundColour = colours.Red3, }, addButton = new RoundedButton { Action = addNew, Size = new Vector2(160, 30), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, }, } }, @@ -132,6 +137,7 @@ protected override void Update() base.Update(); addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time; + table.Padding = new MarginPadding { Bottom = controls.DrawHeight }; } private void goToCurrentGroup() diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index dd0cf2116e7e..fd812cfe2b44 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -28,6 +28,12 @@ public partial class ControlPointTable : CompositeDrawable { public BindableList Groups { get; } = new BindableList(); + public new MarginPadding Padding + { + get => base.Padding; + set => base.Padding = value; + } + [Cached] private Bindable activeTimingPoint { get; } = new Bindable(); @@ -53,7 +59,6 @@ public partial class ControlPointTable : CompositeDrawable private void load(OverlayColourProvider colours) { RelativeSizeAxes = Axes.Both; - Padding = new MarginPadding { Bottom = 50 }; InternalChildren = new Drawable[] { From 19e4cc84d58f50d8e10e9c6d3c78133f47047830 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Sep 2024 01:58:09 +0900 Subject: [PATCH 569/579] Also schedule a re-check on download failure --- osu.Desktop/Updater/VelopackUpdateManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index c2965428f7ea..ae58a8793c2d 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -105,6 +105,7 @@ private async Task checkForUpdateAsync(UpdateProgressNotification? notific catch (Exception e) { // In the case of an error, a separate notification will be displayed. + scheduleRecheck = true; notification.FailDownload(); Logger.Error(e, @"update failed!"); } From d451415d5c6b726a10dcedebca6872f63a8273f1 Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Mon, 9 Sep 2024 03:28:50 -0400 Subject: [PATCH 570/579] Move environment variable logic to static bool in OsuGameDesktop --- osu.Desktop/OsuGameDesktop.cs | 6 +++--- osu.Desktop/Program.cs | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index c75a3f0a1af5..46bd894c07e2 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -95,11 +95,11 @@ public OsuGameDesktop(string[]? args = null) return key?.OpenSubKey(WindowsAssociationManager.SHELL_OPEN_COMMAND)?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); } + public static bool IsPackageManaged => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER")); + protected override UpdateManager CreateUpdateManager() { - string? packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER"); - - if (!string.IsNullOrEmpty(packageManaged)) + if (IsPackageManaged) return new NoActionUpdateManager(); return new VelopackUpdateManager(); diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 78a20e32dcda..6e0234f387df 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -169,9 +169,7 @@ private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args) private static void setupVelopack() { - string? packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER"); - - if (!string.IsNullOrEmpty(packageManaged)) + if (OsuGameDesktop.IsPackageManaged) { Logger.Log("Updates are being managed by an external provider. Skipping Velopack setup"); return; From 63b6f36a29e7e1f4d8d19f945155c29c1013c4af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Sep 2024 18:09:28 +0900 Subject: [PATCH 571/579] Add missing `.` --- osu.Desktop/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 6e0234f387df..ebc7509af6ea 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -171,7 +171,7 @@ private static void setupVelopack() { if (OsuGameDesktop.IsPackageManaged) { - Logger.Log("Updates are being managed by an external provider. Skipping Velopack setup"); + Logger.Log("Updates are being managed by an external provider. Skipping Velopack setup."); return; } From 4ada0bf787c1d1a17a14eadaac2f464c66f7227c Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Mon, 9 Sep 2024 12:48:15 -0400 Subject: [PATCH 572/579] Differentiate lazer in menus --- osu.Desktop/osu.Desktop.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 3588317b8a8b..841672b5815d 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -5,6 +5,7 @@ true A free-to-win rhythm game. Rhythm is just a *click* away! osu! + osu!lazer osu! osu!(lazer) lazer.ico From f716cb4a7cd0b1edfd52afa4d9533ed2337372cf Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Mon, 9 Sep 2024 16:11:28 -0400 Subject: [PATCH 573/579] Change to using osu!(lazer) --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 841672b5815d..bf5f26b352a7 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -5,7 +5,7 @@ true A free-to-win rhythm game. Rhythm is just a *click* away! osu! - osu!lazer + osu!(lazer) osu! osu!(lazer) lazer.ico From d8a745ec045d95fb5a8ca7785185a70b43054baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Sep 2024 11:24:11 +0200 Subject: [PATCH 574/579] Decouple legacy mania combo counter from abstract eldritch entity --- .../Legacy/LegacyManiaComboCounter.cs | 143 +++++++++++++++--- 1 file changed, 123 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 07d014b416fe..889e6326f7e4 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -12,17 +15,76 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { - public partial class LegacyManiaComboCounter : LegacyComboCounter + public partial class LegacyManiaComboCounter : CompositeDrawable, ISerialisableDrawable { + public bool UsesFixedAnchor { get; set; } + + public Bindable Current { get; } = new BindableInt { MinValue = 0 }; + + /// + /// Value shown at the current moment. + /// + public virtual int DisplayedCount + { + get => displayedCount; + private set + { + if (displayedCount.Equals(value)) + return; + + displayedCountText.FadeTo(value == 0 ? 0 : 1); + displayedCountText.Text = value.ToString(CultureInfo.InvariantCulture); + counterContainer.Size = displayedCountText.Size; + + displayedCount = value; + } + } + + private int displayedCount; + + private int previousValue; + + private const double fade_out_duration = 100; + private const double rolling_duration = 20; + + private Container counterContainer = null!; + private LegacySpriteText popOutCountText = null!; + private LegacySpriteText displayedCountText = null!; + [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, ScoreProcessor scoreProcessor) { - DisplayedCountText.Anchor = Anchor.Centre; - DisplayedCountText.Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; - PopOutCountText.Anchor = Anchor.Centre; - PopOutCountText.Origin = Anchor.Centre; - PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; + InternalChildren = new[] + { + counterContainer = new Container + { + AlwaysPresent = true, + Children = new[] + { + popOutCountText = new LegacySpriteText(LegacyFont.Combo) + { + Alpha = 0, + Blending = BlendingParameters.Additive, + BypassAutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red, + }, + displayedCountText = new LegacySpriteText(LegacyFont.Combo) + { + Alpha = 0, + AlwaysPresent = true, + BypassAutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + } + }; + + Current.BindTo(scoreProcessor.Combo); } [Resolved] @@ -34,6 +96,12 @@ protected override void LoadComplete() { base.LoadComplete(); + displayedCountText.Text = popOutCountText.Text = Current.Value.ToString(CultureInfo.InvariantCulture); + + Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); + + counterContainer.Size = displayedCountText.Size; + direction = scrollingInfo.Direction.GetBoundCopy(); direction.BindValueChanged(_ => updateAnchor()); @@ -56,36 +124,71 @@ private void updateAnchor() Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1); } - protected override void OnCountIncrement() + private void updateCount(bool rolling) + { + int prev = previousValue; + previousValue = Current.Value; + + if (!IsLoaded) + return; + + if (!rolling) + { + FinishTransforms(false, nameof(DisplayedCount)); + + if (prev + 1 == Current.Value) + onCountIncrement(); + else + onCountChange(); + } + else + onCountRolling(); + } + + private void onCountIncrement() { - base.OnCountIncrement(); + popOutCountText.Hide(); - PopOutCountText.Hide(); - DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f)) + DisplayedCount = Current.Value; + displayedCountText.ScaleTo(new Vector2(1f, 1.4f)) .ScaleTo(new Vector2(1f), 300, Easing.Out) .FadeIn(120); } - protected override void OnCountChange() + private void onCountChange() { - base.OnCountChange(); + popOutCountText.Hide(); + + if (Current.Value == 0) + displayedCountText.FadeOut(); + + DisplayedCount = Current.Value; - PopOutCountText.Hide(); - DisplayedCountText.ScaleTo(1f); + displayedCountText.ScaleTo(1f); } - protected override void OnCountRolling() + private void onCountRolling() { if (DisplayedCount > 0) { - PopOutCountText.Text = FormatCount(DisplayedCount); - PopOutCountText.FadeTo(0.8f).FadeOut(200) + popOutCountText.Text = DisplayedCount.ToString(CultureInfo.InvariantCulture); + popOutCountText.FadeTo(0.8f).FadeOut(200) .ScaleTo(1f).ScaleTo(4f, 200); - DisplayedCountText.FadeTo(0.5f, 300); + displayedCountText.FadeTo(0.5f, 300); } - base.OnCountRolling(); + // Hides displayed count if was increasing from 0 to 1 but didn't finish + if (DisplayedCount == 0 && Current.Value == 0) + displayedCountText.FadeOut(fade_out_duration); + + this.TransformTo(nameof(DisplayedCount), Current.Value, getProportionalDuration(DisplayedCount, Current.Value)); + } + + private double getProportionalDuration(int currentValue, int newValue) + { + double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; + return difference * rolling_duration; } } } From 0e663d18014436c6bcb1d3ba99c6e66f7d299a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Sep 2024 11:27:21 +0200 Subject: [PATCH 575/579] Revert default combo counter code to pre-abstractification (and nuke eldritch abstract entity) --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 8 +- osu.Game/Skinning/LegacyComboCounter.cs | 203 -------------- .../Skinning/LegacyDefaultComboCounter.cs | 260 +++++++++++++++--- 3 files changed, 232 insertions(+), 239 deletions(-) delete mode 100644 osu.Game/Skinning/LegacyComboCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 3a7bc0530010..91188f5bacd8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -440,8 +440,8 @@ public void TestMigrationLegacy() AddStep("import old classic skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"classic-layout-version-0.osk").SkinInfo); AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); - AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); - AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyDefaultComboCounter { @@ -454,8 +454,8 @@ public void TestMigrationLegacy() AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault()); AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin); AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); - AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); - AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); } private Skin importSkinFromArchives(string filename) diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs deleted file mode 100644 index 7003e0d3c83a..000000000000 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Skinning -{ - /// - /// Uses the 'x' symbol and has a pop-out effect while rolling over. - /// - public abstract partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable - { - public Bindable Current { get; } = new BindableInt { MinValue = 0 }; - - private const double fade_out_duration = 100; - - /// - /// Duration in milliseconds for the counter roll-up animation for each element. - /// - private const double rolling_duration = 20; - - protected readonly LegacySpriteText PopOutCountText; - protected readonly LegacySpriteText DisplayedCountText; - - private int previousValue; - - private int displayedCount; - - private bool isRolling; - - private readonly Container counterContainer; - - public bool UsesFixedAnchor { get; set; } - - protected LegacyComboCounter() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new[] - { - counterContainer = new Container - { - AlwaysPresent = true, - Children = new[] - { - PopOutCountText = new LegacySpriteText(LegacyFont.Combo) - { - Alpha = 0, - Blending = BlendingParameters.Additive, - BypassAutoSizeAxes = Axes.Both, - }, - DisplayedCountText = new LegacySpriteText(LegacyFont.Combo) - { - Alpha = 0, - AlwaysPresent = true, - BypassAutoSizeAxes = Axes.Both, - }, - } - } - }; - } - - /// - /// Value shown at the current moment. - /// - public virtual int DisplayedCount - { - get => displayedCount; - private set - { - if (displayedCount.Equals(value)) - return; - - if (isRolling) - onDisplayedCountRolling(value); - else if (displayedCount + 1 == value) - onDisplayedCountIncrement(value); - else - onDisplayedCountChange(value); - - displayedCount = value; - } - } - - [BackgroundDependencyLoader] - private void load(ScoreProcessor scoreProcessor) - { - Current.BindTo(scoreProcessor.Combo); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - DisplayedCountText.Text = FormatCount(Current.Value); - PopOutCountText.Text = FormatCount(Current.Value); - - Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); - - counterContainer.Size = DisplayedCountText.Size; - } - - private void updateCount(bool rolling) - { - int prev = previousValue; - previousValue = Current.Value; - - if (!IsLoaded) - return; - - if (!rolling) - { - FinishTransforms(false, nameof(DisplayedCount)); - - isRolling = false; - DisplayedCount = prev; - - if (prev + 1 == Current.Value) - OnCountIncrement(); - else - OnCountChange(); - } - else - { - OnCountRolling(); - isRolling = true; - } - } - - /// - /// Raised when the counter should display the new value with transitions. - /// - protected virtual void OnCountIncrement() - { - if (DisplayedCount < Current.Value - 1) - DisplayedCount++; - - DisplayedCount++; - } - - /// - /// Raised when the counter should roll to the new combo value (usually roll back to zero). - /// - protected virtual void OnCountRolling() - { - // Hides displayed count if was increasing from 0 to 1 but didn't finish - if (DisplayedCount == 0 && Current.Value == 0) - DisplayedCountText.FadeOut(fade_out_duration); - - transformRoll(DisplayedCount, Current.Value); - } - - /// - /// Raised when the counter should display the new combo value without any transitions. - /// - protected virtual void OnCountChange() - { - if (Current.Value == 0) - DisplayedCountText.FadeOut(); - - DisplayedCount = Current.Value; - } - - private void onDisplayedCountRolling(int newValue) - { - if (newValue == 0) - DisplayedCountText.FadeOut(fade_out_duration); - - DisplayedCountText.Text = FormatCount(newValue); - counterContainer.Size = DisplayedCountText.Size; - } - - private void onDisplayedCountChange(int newValue) - { - DisplayedCountText.FadeTo(newValue == 0 ? 0 : 1); - DisplayedCountText.Text = FormatCount(newValue); - - counterContainer.Size = DisplayedCountText.Size; - } - - private void onDisplayedCountIncrement(int newValue) - { - DisplayedCountText.Text = FormatCount(newValue); - - counterContainer.Size = DisplayedCountText.Size; - } - - private void transformRoll(int currentValue, int newValue) => - this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue)); - - protected virtual string FormatCount(int count) => $@"{count}"; - - private double getProportionalDuration(int currentValue, int newValue) - { - double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; - return difference * rolling_duration; - } - } -} diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs index 6c81b1f959a5..7de4aee65635 100644 --- a/osu.Game/Skinning/LegacyDefaultComboCounter.cs +++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs @@ -1,8 +1,12 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Threading; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Skinning @@ -10,73 +14,265 @@ namespace osu.Game.Skinning /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public partial class LegacyDefaultComboCounter : LegacyComboCounter + public partial class LegacyDefaultComboCounter : CompositeDrawable, ISerialisableDrawable { + public Bindable Current { get; } = new BindableInt { MinValue = 0 }; + + private uint scheduledPopOutCurrentId; + private const double big_pop_out_duration = 300; + private const double small_pop_out_duration = 100; - private ScheduledDelegate? scheduledPopOut; + private const double fade_out_duration = 100; + + /// + /// Duration in milliseconds for the counter roll-up animation for each element. + /// + private const double rolling_duration = 20; + + private readonly Drawable popOutCount; + + private readonly Drawable displayedCountSpriteText; + + private int previousValue; + + private int displayedCount; + + private bool isRolling; + + private readonly Container counterContainer; + + public bool UsesFixedAnchor { get; set; } public LegacyDefaultComboCounter() { + AutoSizeAxes = Axes.Both; + + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + Margin = new MarginPadding(10); - PopOutCountText.Anchor = Anchor.BottomLeft; - DisplayedCountText.Anchor = Anchor.BottomLeft; + Scale = new Vector2(1.28f); + + InternalChildren = new[] + { + counterContainer = new Container + { + AlwaysPresent = true, + Children = new[] + { + popOutCount = new LegacySpriteText(LegacyFont.Combo) + { + Alpha = 0, + Blending = BlendingParameters.Additive, + Anchor = Anchor.BottomLeft, + BypassAutoSizeAxes = Axes.Both, + }, + displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) + { + Alpha = 0, + AlwaysPresent = true, + Anchor = Anchor.BottomLeft, + BypassAutoSizeAxes = Axes.Both, + }, + } + } + }; + } + + /// + /// Value shown at the current moment. + /// + public virtual int DisplayedCount + { + get => displayedCount; + private set + { + if (displayedCount.Equals(value)) + return; + + if (isRolling) + onDisplayedCountRolling(value); + else if (displayedCount + 1 == value) + onDisplayedCountIncrement(value); + else + onDisplayedCountChange(value); + + displayedCount = value; + } + } + + [BackgroundDependencyLoader] + private void load(ScoreProcessor scoreProcessor) + { + Current.BindTo(scoreProcessor.Combo); } protected override void LoadComplete() { base.LoadComplete(); + ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value); + ((IHasText)popOutCount).Text = formatCount(Current.Value); + + Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); + + updateLayout(); + } + + private void updateLayout() + { const float font_height_ratio = 0.625f; const float vertical_offset = 9; - DisplayedCountText.OriginPosition = new Vector2(0, font_height_ratio * DisplayedCountText.Height + vertical_offset); - DisplayedCountText.Position = new Vector2(0, -(1 - font_height_ratio) * DisplayedCountText.Height + vertical_offset); + displayedCountSpriteText.OriginPosition = new Vector2(0, font_height_ratio * displayedCountSpriteText.Height + vertical_offset); + displayedCountSpriteText.Position = new Vector2(0, -(1 - font_height_ratio) * displayedCountSpriteText.Height + vertical_offset); + + popOutCount.OriginPosition = new Vector2(3, font_height_ratio * popOutCount.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left + popOutCount.Position = new Vector2(0, -(1 - font_height_ratio) * popOutCount.Height + vertical_offset); - PopOutCountText.OriginPosition = new Vector2(3, font_height_ratio * PopOutCountText.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left - PopOutCountText.Position = new Vector2(0, -(1 - font_height_ratio) * PopOutCountText.Height + vertical_offset); + counterContainer.Size = displayedCountSpriteText.Size; } - protected override void OnCountIncrement() + private void updateCount(bool rolling) { - DisplayedCountText.Show(); + int prev = previousValue; + previousValue = Current.Value; - PopOutCountText.Text = FormatCount(Current.Value); + if (!IsLoaded) + return; - PopOutCountText.ScaleTo(1.56f) - .ScaleTo(1, big_pop_out_duration); + if (!rolling) + { + FinishTransforms(false, nameof(DisplayedCount)); + isRolling = false; + DisplayedCount = prev; - PopOutCountText.FadeTo(0.6f) - .FadeOut(big_pop_out_duration); + if (prev + 1 == Current.Value) + onCountIncrement(prev, Current.Value); + else + onCountChange(Current.Value); + } + else + { + onCountRolling(displayedCount, Current.Value); + isRolling = true; + } + } - this.Delay(big_pop_out_duration - 140).Schedule(() => + private void transformPopOut(int newValue) + { + ((IHasText)popOutCount).Text = formatCount(newValue); + + popOutCount.ScaleTo(1.56f) + .ScaleTo(1, big_pop_out_duration); + + popOutCount.FadeTo(0.6f) + .FadeOut(big_pop_out_duration); + } + + private void transformNoPopOut(int newValue) + { + ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); + + counterContainer.Size = displayedCountSpriteText.Size; + + displayedCountSpriteText.ScaleTo(1); + } + + private void transformPopOutSmall(int newValue) + { + ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); + + counterContainer.Size = displayedCountSpriteText.Size; + + displayedCountSpriteText.ScaleTo(1).Then() + .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then() + .ScaleTo(1, small_pop_out_duration / 2, Easing.Out); + } + + private void scheduledPopOutSmall(uint id) + { + // Too late; scheduled task invalidated + if (id != scheduledPopOutCurrentId) + return; + + DisplayedCount++; + } + + private void onCountIncrement(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + if (DisplayedCount < currentValue) + DisplayedCount++; + + displayedCountSpriteText.Show(); + + transformPopOut(newValue); + + uint newTaskId = scheduledPopOutCurrentId; + + Scheduler.AddDelayed(delegate { - base.OnCountIncrement(); + scheduledPopOutSmall(newTaskId); + }, big_pop_out_duration - 140); + } + + private void onCountRolling(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + // Hides displayed count if was increasing from 0 to 1 but didn't finish + if (currentValue == 0 && newValue == 0) + displayedCountSpriteText.FadeOut(fade_out_duration); + + transformRoll(currentValue, newValue); + } - DisplayedCountText.ScaleTo(1).Then() - .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then() - .ScaleTo(1, small_pop_out_duration / 2, Easing.Out); - }, out scheduledPopOut); + private void onCountChange(int newValue) + { + scheduledPopOutCurrentId++; + + if (newValue == 0) + displayedCountSpriteText.FadeOut(); + + DisplayedCount = newValue; } - protected override void OnCountRolling() + private void onDisplayedCountRolling(int newValue) { - scheduledPopOut?.Cancel(); - scheduledPopOut = null; + if (newValue == 0) + displayedCountSpriteText.FadeOut(fade_out_duration); + else + displayedCountSpriteText.Show(); - base.OnCountRolling(); + transformNoPopOut(newValue); } - protected override void OnCountChange() + private void onDisplayedCountChange(int newValue) { - scheduledPopOut?.Cancel(); - scheduledPopOut = null; + displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); + transformNoPopOut(newValue); + } - base.OnCountChange(); + private void onDisplayedCountIncrement(int newValue) + { + displayedCountSpriteText.Show(); + transformPopOutSmall(newValue); } - protected override string FormatCount(int count) => $@"{count}x"; + private void transformRoll(int currentValue, int newValue) => + this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue)); + + private string formatCount(int count) => $@"{count}x"; + + private double getProportionalDuration(int currentValue, int newValue) + { + double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; + return difference * rolling_duration; + } } } From b78ef81bf1e5e4be82dac4302936d842f9d9bb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Sep 2024 15:54:07 +0200 Subject: [PATCH 576/579] Fix Flashlight not appearing on top of bubbles from Bubbles mod Inadvertently regressed in 44d0dc6113a408a15a18025325e55646a2147b14. --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index c924915bd086..64c193d25f2d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -83,8 +83,6 @@ public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) flashlight.RelativeSizeAxes = Axes.Both; flashlight.Colour = Color4.Black; - // Flashlight mods should always draw above any other mod adding overlays. - flashlight.Depth = float.MinValue; flashlight.Combo.BindTo(Combo); flashlight.GetPlayfieldScale = () => drawableRuleset.Playfield.Scale; @@ -95,6 +93,9 @@ public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) // workaround for 1px gaps on the edges of the playfield which would sometimes show with "gameplay" screen scaling active. Padding = new MarginPadding(-1), Child = flashlight, + // Flashlight mods should always draw above any other mod adding overlays. + // NegativeInfinity is not used to allow one more thing drawn on top (used in replay analysis overlay in osu!). + Depth = float.MinValue, }); } From 4a39873e2aac3a6fa71a3be06407d9afb7df8922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Sep 2024 15:54:30 +0200 Subject: [PATCH 577/579] Fix replay analysis overlay not rotating with Barrel Roll enabled Closes https://github.com/ppy/osu/issues/29839. --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 3 ++- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 16edc654a7dd..4192d678dd8a 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -44,7 +44,8 @@ private void load(ReplayPlayer? replayPlayer) { if (replayPlayer != null) { - PlayfieldAdjustmentContainer.Add(new ReplayAnalysisOverlay(replayPlayer.Score.Replay)); + ReplayAnalysisOverlay analysisOverlay; + PlayfieldAdjustmentContainer.Add(analysisOverlay = new ReplayAnalysisOverlay(replayPlayer.Score.Replay)); replayPlayer.AddSettings(new ReplayAnalysisSettings(Config)); cursorHideEnabled = Config.GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 0c301d293f4b..4f90496308d1 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -40,9 +40,11 @@ public abstract class ModBarrelRoll : Mod, IUpdatableByPlayfield, IAppl public override string SettingDescription => $"{SpinSpeed.Value:N2} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; + private PlayfieldAdjustmentContainer playfieldAdjustmentContainer = null!; + public void Update(Playfield playfield) { - playfield.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + playfieldAdjustmentContainer.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -52,7 +54,9 @@ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) var playfieldSize = drawableRuleset.Playfield.DrawSize; float minSide = MathF.Min(playfieldSize.X, playfieldSize.Y); float maxSide = MathF.Max(playfieldSize.X, playfieldSize.Y); - drawableRuleset.Playfield.Scale = new Vector2(minSide / maxSide); + + playfieldAdjustmentContainer = drawableRuleset.PlayfieldAdjustmentContainer; + playfieldAdjustmentContainer.Scale = new Vector2(minSide / maxSide); } } } From f38ae5f239ba215268cfc6bbbf4aa2263fc9845b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Sep 2024 15:55:02 +0200 Subject: [PATCH 578/579] Fix replay analysis overlay being affected by visibility impairing mods Closes https://github.com/ppy/osu/issues/29748. --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 4192d678dd8a..ab69b67051f3 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -46,6 +46,7 @@ private void load(ReplayPlayer? replayPlayer) { ReplayAnalysisOverlay analysisOverlay; PlayfieldAdjustmentContainer.Add(analysisOverlay = new ReplayAnalysisOverlay(replayPlayer.Score.Replay)); + Overlays.Add(analysisOverlay.CreateProxy().With(p => p.Depth = float.NegativeInfinity)); replayPlayer.AddSettings(new ReplayAnalysisSettings(Config)); cursorHideEnabled = Config.GetBindable(OsuRulesetSetting.ReplayCursorHideEnabled); From 77c3cb65045876d2443a8c1aff442347eab81cc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2024 14:38:51 +0900 Subject: [PATCH 579/579] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7b45b9dec455..d5bdfd91b509 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 1d76deddaced..da1cec395faa 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - +