From 0db6c2ada147b5749228d414ff8521e83b69bf96 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 22:38:11 +0300 Subject: [PATCH 001/162] Add enum with font types --- osu.Game/Skinning/DefaultFont.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 osu.Game/Skinning/DefaultFont.cs diff --git a/osu.Game/Skinning/DefaultFont.cs b/osu.Game/Skinning/DefaultFont.cs new file mode 100644 index 0000000000..69b6aaaff4 --- /dev/null +++ b/osu.Game/Skinning/DefaultFont.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.Skinning.Components; + +namespace osu.Game.Skinning +{ + /// + /// The type of built-in font to use for . + /// + public enum DefaultFont + { + Venera, + Torus, + TorusAlt, + Inter + } +} From b41f30c8689757c3b5850000dccefcb85c475cba Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 22:44:54 +0300 Subject: [PATCH 002/162] Allow changing font of text elements --- .../Components/BeatmapAttributeText.cs | 10 ++--- .../Components/DefaultTextSkinComponent.cs | 42 +++++++++++++++++++ osu.Game/Skinning/Components/TextElement.cs | 11 ++--- 3 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Skinning/Components/DefaultTextSkinComponent.cs diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 0a5f0d22cb..71d8f1a40f 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -11,12 +11,11 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Extensions; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; @@ -24,10 +23,8 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public partial class BeatmapAttributeText : Container, ISkinnableDrawable + public partial class BeatmapAttributeText : DefaultTextSkinComponent { - public bool UsesFixedAnchor { get; set; } - [SettingSource("Attribute", "The attribute to be displayed.")] public Bindable Attribute { get; } = new Bindable(BeatmapAttribute.StarRating); @@ -67,7 +64,6 @@ namespace osu.Game.Skinning.Components { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.Default.With(size: 40) } }; } @@ -122,6 +118,8 @@ namespace osu.Game.Skinning.Components text.Text = LocalisableString.Format(numberedTemplate, args); } + + protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); } public enum BeatmapAttribute diff --git a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs new file mode 100644 index 0000000000..aff400c798 --- /dev/null +++ b/osu.Game/Skinning/Components/DefaultTextSkinComponent.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.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Graphics; + +namespace osu.Game.Skinning.Components +{ + /// + /// Skin element that contains text and have ability to control its font. + /// + public abstract partial class DefaultTextSkinComponent : Container, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [SettingSource("Font", "Font to use.")] + public Bindable Font { get; } = new Bindable(DefaultFont.Torus); + + protected abstract void SetFont(FontUsage font); + + protected override void LoadComplete() + { + base.LoadComplete(); + Font.BindValueChanged(e => + { + FontUsage f = e.NewValue switch + { + DefaultFont.Venera => OsuFont.Numeric, + DefaultFont.Torus => OsuFont.Torus, + DefaultFont.TorusAlt => OsuFont.TorusAlternate, + DefaultFont.Inter => OsuFont.Inter, + _ => OsuFont.Default + }; + + SetFont(f); + }, true); + } + } +} diff --git a/osu.Game/Skinning/Components/TextElement.cs b/osu.Game/Skinning/Components/TextElement.cs index 74a0acb979..fb779fdb83 100644 --- a/osu.Game/Skinning/Components/TextElement.cs +++ b/osu.Game/Skinning/Components/TextElement.cs @@ -4,7 +4,7 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -12,17 +12,16 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public partial class TextElement : Container, ISkinnableDrawable + public partial class TextElement : DefaultTextSkinComponent { - public bool UsesFixedAnchor { get; set; } - [SettingSource("Text", "The text to be displayed.")] public Bindable Text { get; } = new Bindable("Circles!"); + private readonly OsuSpriteText text; + public TextElement() { AutoSizeAxes = Axes.Both; - OsuSpriteText text; InternalChildren = new Drawable[] { text = new OsuSpriteText @@ -34,5 +33,7 @@ namespace osu.Game.Skinning.Components }; text.Current.BindTo(Text); } + + protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); } } From 8174f6be64a7877c0e0788028303236ef01e1ec6 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 23:32:17 +0300 Subject: [PATCH 003/162] Get rid of dublicated enum --- .../Components/DefaultTextSkinComponent.cs | 12 ++---------- osu.Game/Skinning/DefaultFont.cs | 18 ------------------ 2 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 osu.Game/Skinning/DefaultFont.cs diff --git a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs index aff400c798..1fd29effbd 100644 --- a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs +++ b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning.Components public bool UsesFixedAnchor { get; set; } [SettingSource("Font", "Font to use.")] - public Bindable Font { get; } = new Bindable(DefaultFont.Torus); + public Bindable Font { get; } = new Bindable(Typeface.Torus); protected abstract void SetFont(FontUsage font); @@ -26,15 +26,7 @@ namespace osu.Game.Skinning.Components base.LoadComplete(); Font.BindValueChanged(e => { - FontUsage f = e.NewValue switch - { - DefaultFont.Venera => OsuFont.Numeric, - DefaultFont.Torus => OsuFont.Torus, - DefaultFont.TorusAlt => OsuFont.TorusAlternate, - DefaultFont.Inter => OsuFont.Inter, - _ => OsuFont.Default - }; - + FontUsage f = OsuFont.GetFont(e.NewValue); SetFont(f); }, true); } diff --git a/osu.Game/Skinning/DefaultFont.cs b/osu.Game/Skinning/DefaultFont.cs deleted file mode 100644 index 69b6aaaff4..0000000000 --- a/osu.Game/Skinning/DefaultFont.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 osu.Game.Skinning.Components; - -namespace osu.Game.Skinning -{ - /// - /// The type of built-in font to use for . - /// - public enum DefaultFont - { - Venera, - Torus, - TorusAlt, - Inter - } -} From b240d15731d7a4205825c78122f298d28cb096c7 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 23:38:50 +0300 Subject: [PATCH 004/162] Fix numeric font --- osu.Game/Skinning/Components/DefaultTextSkinComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs index 1fd29effbd..abe16918c5 100644 --- a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs +++ b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning.Components base.LoadComplete(); Font.BindValueChanged(e => { - FontUsage f = OsuFont.GetFont(e.NewValue); + FontUsage f = OsuFont.GetFont(e.NewValue, weight: e.NewValue == Typeface.Venera ? FontWeight.Bold : FontWeight.Regular); SetFont(f); }, true); } From 7d7b824f568ac35602b0e39e38223412c4252ded Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 23:42:16 +0300 Subject: [PATCH 005/162] Add description for torus alt --- osu.Game/Graphics/OsuFont.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 038ea0f5d7..7aa98ece95 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -3,6 +3,7 @@ #nullable disable +using System.ComponentModel; using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics @@ -115,6 +116,8 @@ namespace osu.Game.Graphics { Venera, Torus, + + [Description("Torus (alternate)")] TorusAlternate, Inter, } From 9f4bb3e0cacc3248bf4c76396ad6cb97fca82b46 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 7 Dec 2022 09:51:22 +0100 Subject: [PATCH 006/162] Add segmend end completions to SliderPath Fix segmentEnds incorrect on shortened paths Revert "Add segmend end completions to SliderPath" This reverts commit cd46ca31f9af73541e05f515018847a588b52775. Revert "Fix segmentEnds incorrect on shortened paths" This reverts commit 98a312ca9661c8d6a61141a4be57265a93b79a2a. From 10b59007107594402c946ba071164ff8975a717a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 3 Nov 2022 12:25:23 +0100 Subject: [PATCH 007/162] made PathControlPointVisualiser generic --- .../TestScenePathControlPointVisualiser.cs | 4 +- .../TestSceneSliderControlPointPiece.cs | 14 ++-- .../TestSceneSliderSelectionBlueprint.cs | 2 +- .../Editor/TestSceneSliderSnapping.cs | 8 +- .../Editor/TestSceneSliderSplitting.cs | 8 +- .../PathControlPointConnectionPiece.cs | 28 ++++--- .../Components/PathControlPointPiece.cs | 45 +++++----- .../Components/PathControlPointVisualiser.cs | 82 +++++++++---------- .../Sliders/SliderPlacementBlueprint.cs | 4 +- .../Sliders/SliderSelectionBlueprint.cs | 4 +- .../Editing/TestSceneBlueprintOrdering.cs | 2 +- .../Editing/TestSceneComposerSelection.cs | 2 +- 12 files changed, 103 insertions(+), 100 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index d1a04e28e5..37561fda85 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public partial class TestScenePathControlPointVisualiser : OsuManualInputManagerTestScene { private Slider slider; - private PathControlPointVisualiser visualiser; + private PathControlPointVisualiser visualiser; [SetUp] public void Setup() => Schedule(() => @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPathType(3, null); } - private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) + private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 112aab884b..db9eea4127 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void assertSelectionCount(int count) => - AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == count); + AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == count); private void assertSelected(int index) => AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected", - () => this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value); + () => this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value); private void moveMouseToRelativePosition(Vector2 relativePosition) => AddStep($"move mouse to {relativePosition}", () => @@ -202,12 +202,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToControlPoint(2); AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); - AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); addMovementStep(new Vector2(450, 50)); AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); assertControlPointPosition(2, new Vector2(450, 50)); assertControlPointType(2, PathType.PerfectCurve); @@ -236,12 +236,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToControlPoint(3); AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); - AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); addMovementStep(new Vector2(550, 50)); AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); // note: if the head is part of the selection being moved, the entire slider is moved. // the unselected nodes will therefore change position relative to the slider head. @@ -354,7 +354,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; - public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; + public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) : base(slider) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index ad740b2977..8ed77d45d7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; - public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; + public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) : base(slider) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index e9d50d5118..f262a4334a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestMovingUnsnappedSliderNodesSnaps() { - PathControlPointPiece sliderEnd = null; + PathControlPointPiece sliderEnd = null; assertSliderSnapped(false); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("select slider end", () => { - sliderEnd = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last()); + sliderEnd = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last()); InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre); }); AddStep("move slider end", () => @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("move mouse to new point location", () => { - var firstPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]); + var firstPiece = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]); var pos = slider.Path.PositionAt(0.25d) + slider.Position; InputManager.MoveMouseTo(firstPiece.Parent.ToScreenSpace(pos)); }); @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("move mouse to second control point", () => { - var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]); + var secondPiece = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]); InputManager.MoveMouseTo(secondPiece); }); AddStep("quick delete", () => diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index b2ac462c8f..6cb77c7b92 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor => Editor.ChildrenOfType().First(); private Slider? slider; - private PathControlPointVisualiser? visualiser; + private PathControlPointVisualiser? visualiser; private const double split_gap = 100; @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select added slider", () => { EditorBeatmap.SelectedHitObjects.Add(slider); - visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First(); + visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First(); }); moveMouseToControlPoint(2); @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select added slider", () => { EditorBeatmap.SelectedHitObjects.Add(slider); - visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First(); + visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First(); }); moveMouseToControlPoint(2); @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select added slider", () => { EditorBeatmap.SelectedHitObjects.Add(slider); - visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First(); + visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First(); }); moveMouseToControlPoint(2); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 28e0d650c4..67685d21a7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -8,34 +8,36 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { /// - /// A visualisation of the line between two s. + /// A visualisation of the line between two s. /// - public partial class PathControlPointConnectionPiece : CompositeDrawable + /// The type of which this visualises. + public partial class PathControlPointConnectionPiece : CompositeDrawable where T : OsuHitObject, IHasPath { public readonly PathControlPoint ControlPoint; private readonly Path path; - private readonly Slider slider; + private readonly T hitObject; public int ControlPointIndex { get; set; } - private IBindable sliderPosition; + private IBindable hitObjectPosition; private IBindable pathVersion; - public PathControlPointConnectionPiece(Slider slider, int controlPointIndex) + public PathControlPointConnectionPiece(T hitObject, int controlPointIndex) { - this.slider = slider; + this.hitObject = hitObject; ControlPointIndex = controlPointIndex; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; - ControlPoint = slider.Path.ControlPoints[controlPointIndex]; + ControlPoint = hitObject.Path.ControlPoints[controlPointIndex]; InternalChild = path = new SmoothPath { @@ -48,10 +50,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.LoadComplete(); - sliderPosition = slider.PositionBindable.GetBoundCopy(); - sliderPosition.BindValueChanged(_ => updateConnectingPath()); + hitObjectPosition = hitObject.PositionBindable.GetBoundCopy(); + hitObjectPosition.BindValueChanged(_ => updateConnectingPath()); - pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion = hitObject.Path.Version.GetBoundCopy(); pathVersion.BindValueChanged(_ => updateConnectingPath()); updateConnectingPath(); @@ -62,16 +64,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// private void updateConnectingPath() { - Position = slider.StackedPosition + ControlPoint.Position; + Position = hitObject.StackedPosition + ControlPoint.Position; path.ClearVertices(); int nextIndex = ControlPointIndex + 1; - if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count) + if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count) return; path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[nextIndex].Position - ControlPoint.Position); + path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position); path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); } 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 d83f35d13f..a4d5c08b8a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -29,11 +29,12 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { /// - /// A visualisation of a single in a . + /// A visualisation of a single in an osu hit object with a path. /// - public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip + /// The type of which this visualises. + public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip where T : OsuHitObject, IHasPath { - public Action RequestSelection; + public Action, MouseButtonEvent> RequestSelection; public Action DragStarted; public Action DragInProgress; @@ -44,34 +45,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public readonly BindableBool IsSelected = new BindableBool(); public readonly PathControlPoint ControlPoint; - private readonly Slider slider; + private readonly T hitObject; private readonly Container marker; private readonly Drawable markerRing; [Resolved] private OsuColour colours { get; set; } - private IBindable sliderPosition; - private IBindable sliderScale; + private IBindable hitObjectPosition; + private IBindable hitObjectScale; [UsedImplicitly] - private readonly IBindable sliderVersion; + private readonly IBindable hitObjectVersion; - public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) + public PathControlPointPiece(T hitObject, PathControlPoint controlPoint) { - this.slider = slider; + this.hitObject = hitObject; ControlPoint = controlPoint; - // we don't want to run the path type update on construction as it may inadvertently change the slider. - cachePoints(slider); + // we don't want to run the path type update on construction as it may inadvertently change the hit object. + cachePoints(hitObject); - sliderVersion = slider.Path.Version.GetBoundCopy(); + hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); // schedule ensure that updates are only applied after all operations from a single frame are applied. - // this avoids inadvertently changing the slider path type for batch operations. - sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() => + // this avoids inadvertently changing the hit object path type for batch operations. + hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() => { - cachePoints(slider); + cachePoints(hitObject); updatePathType(); })); @@ -120,11 +121,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.LoadComplete(); - sliderPosition = slider.PositionBindable.GetBoundCopy(); - sliderPosition.BindValueChanged(_ => updateMarkerDisplay()); + hitObjectPosition = hitObject.PositionBindable.GetBoundCopy(); + hitObjectPosition.BindValueChanged(_ => updateMarkerDisplay()); - sliderScale = slider.ScaleBindable.GetBoundCopy(); - sliderScale.BindValueChanged(_ => updateMarkerDisplay()); + hitObjectScale = hitObject.ScaleBindable.GetBoundCopy(); + hitObjectScale.BindValueChanged(_ => updateMarkerDisplay()); IsSelected.BindValueChanged(_ => updateMarkerDisplay()); @@ -212,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke(); - private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint); + private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint); /// /// Handles correction of invalid path types. @@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// private void updateMarkerDisplay() { - Position = slider.StackedPosition + ControlPoint.Position; + Position = hitObject.StackedPosition + ControlPoint.Position; markerRing.Alpha = IsSelected.Value ? 1 : 0; @@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components colour = colour.Lighten(1); marker.Colour = colour; - marker.Scale = new Vector2(slider.Scale); + marker.Scale = new Vector2(hitObject.Scale); } private Color4 getColourFromNodeType() 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 3a175888d9..65b212e976 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -29,15 +29,15 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu where T : OsuHitObject, IHasPath { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield. - internal readonly Container Pieces; - internal readonly Container Connections; + internal readonly Container> Pieces; + internal readonly Container> Connections; private readonly IBindableList controlPoints = new BindableList(); - private readonly Slider slider; + private readonly T hitObject; private readonly bool allowSelection; private InputManager inputManager; @@ -48,17 +48,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IDistanceSnapProvider snapProvider { get; set; } - public PathControlPointVisualiser(Slider slider, bool allowSelection) + public PathControlPointVisualiser(T hitObject, bool allowSelection) { - this.slider = slider; + this.hitObject = hitObject; this.allowSelection = allowSelection; RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { - Connections = new Container { RelativeSizeAxes = Axes.Both }, - Pieces = new Container { RelativeSizeAxes = Axes.Both } + Connections = new Container> { RelativeSizeAxes = Axes.Both }, + Pieces = new Container> { RelativeSizeAxes = Axes.Both } }; } @@ -69,12 +69,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components inputManager = GetContainingInputManager(); controlPoints.CollectionChanged += onControlPointsChanged; - controlPoints.BindTo(slider.Path.ControlPoints); + controlPoints.BindTo(hitObject.Path.ControlPoints); } /// - /// Selects the corresponding to the given , - /// and deselects all other s. + /// Selects the corresponding to the given , + /// and deselects all other s. /// public void SetSelectionTo(PathControlPoint pathControlPoint) { @@ -124,8 +124,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return true; } - private bool isSplittable(PathControlPointPiece p) => - // A slider can only be split on control points which connect two different slider segments. + private bool isSplittable(PathControlPointPiece p) => + // A hit object can only be split on control points which connect two different path segments. p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault(); private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { var point = (PathControlPoint)e.NewItems[i]; - Pieces.Add(new PathControlPointPiece(slider, point).With(d => + Pieces.Add(new PathControlPointPiece(hitObject, point).With(d => { if (allowSelection) d.RequestSelection = selectionRequested; @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components d.DragEnded = dragEnded; })); - Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i)); + Connections.Add(new PathControlPointConnectionPiece(hitObject, e.NewStartingIndex + i)); } break; @@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { } - private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e) + private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e) { if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) piece.IsSelected.Toggle(); @@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// /// The control point piece that we want to change the path type of. /// The path type we want to assign to the given control point piece. - private void updatePathType(PathControlPointPiece piece, PathType? type) + private void updatePathType(PathControlPointPiece piece, PathType? type) { int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); @@ -264,9 +264,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void dragStarted(PathControlPoint controlPoint) { - dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray(); - dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray(); - draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint); + dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray(); + dragPathTypes = hitObject.Path.ControlPoints.Select(point => point.Type).ToArray(); + draggedControlPointIndex = hitObject.Path.ControlPoints.IndexOf(controlPoint); selectedControlPoints = new HashSet(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint)); Debug.Assert(draggedControlPointIndex >= 0); @@ -276,25 +276,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void dragInProgress(DragEvent e) { - Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray(); - var oldPosition = slider.Position; - double oldStartTime = slider.StartTime; + Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray(); + var oldPosition = hitObject.Position; + double oldStartTime = hitObject.StartTime; - if (selectedControlPoints.Contains(slider.Path.ControlPoints[0])) + if (selectedControlPoints.Contains(hitObject.Path.ControlPoints[0])) { - // Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account + // Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex])); var result = snapProvider?.FindSnappedPositionAndTime(newHeadPosition); - Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position; + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position; - slider.Position += movementDelta; - slider.StartTime = result?.Time ?? slider.StartTime; + hitObject.Position += movementDelta; + hitObject.StartTime = result?.Time ?? hitObject.StartTime; - for (int i = 1; i < slider.Path.ControlPoints.Count; i++) + for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++) { - var controlPoint = slider.Path.ControlPoints[i]; - // Since control points are relative to the position of the slider, all points that are _not_ selected + var controlPoint = hitObject.Path.ControlPoints[i]; + // Since control points are relative to the position of the hit object, all points that are _not_ selected // need to be offset _back_ by the delta corresponding to the movement of the head point. // All other selected control points (if any) will move together with the head point // (and so they will not move at all, relative to each other). @@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition)); - Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position; + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position; for (int i = 0; i < controlPoints.Count; ++i) { @@ -317,23 +317,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } // Snap the path to the current beat divisor before checking length validity. - slider.SnapTo(snapProvider); + hitObject.SnapTo(snapProvider); - if (!slider.Path.HasValidLength) + if (!hitObject.Path.HasValidLength) { - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Position = oldControlPoints[i]; + for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++) + hitObject.Path.ControlPoints[i].Position = oldControlPoints[i]; - slider.Position = oldPosition; - slider.StartTime = oldStartTime; + hitObject.Position = oldPosition; + hitObject.StartTime = oldStartTime; // Snap the path length again to undo the invalid length. - slider.SnapTo(snapProvider); + hitObject.SnapTo(snapProvider); return; } // Maintain the path types in case they got defaulted to bezier at some point during the drag. - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Type = dragPathTypes[i]; + for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++) + hitObject.Path.ControlPoints[i].Type = dragPathTypes[i]; } private void dragEnded() => changeHandler?.EndChange(); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index f91d35e2e1..77393efeb3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private SliderBodyPiece bodyPiece; private HitCirclePiece headCirclePiece; private HitCirclePiece tailCirclePiece; - private PathControlPointVisualiser controlPointVisualiser; + private PathControlPointVisualiser controlPointVisualiser; private InputManager inputManager; @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) + controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) }; setState(SliderPlacementState.Initial); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a51c223785..b37041674e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected SliderCircleOverlay TailOverlay { get; private set; } [CanBeNull] - protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } + protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } [Resolved(CanBeNull = true)] private IDistanceSnapProvider snapProvider { get; set; } @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { if (ControlPointVisualiser == null) { - AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true) + AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true) { RemoveControlPointsRequested = removeControlPoints, SplitControlPointsRequested = splitControlPoints diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs index 7728adecae..8b598a6a24 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to common point", () => { - var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; + var pos = blueprintContainer.ChildrenOfType>().ElementAt(1).ScreenSpaceDrawQuad.Centre; InputManager.MoveMouseTo(pos); }); AddStep("right click", () => InputManager.Click(MouseButton.Right)); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index ffb8a67c68..b14025c9d8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to controlpoint", () => { - var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; + var pos = blueprintContainer.ChildrenOfType>().ElementAt(1).ScreenSpaceDrawQuad.Centre; InputManager.MoveMouseTo(pos); }); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); From d458c3a012264fa8095db2d24345ce31dfbf3fe1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 7 Dec 2022 10:11:57 +0100 Subject: [PATCH 008/162] Fix variable which didnt get renamed --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 65b212e976..10ee7a9a05 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -248,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components break; } - slider.Path.ExpectedDistance.Value = null; + hitObject.Path.ExpectedDistance.Value = null; piece.ControlPoint.Type = type; } From 83b8d8ad8cb8cb00cf58ba148fb427b0bde86e94 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 1 Jan 2023 16:48:47 -0800 Subject: [PATCH 009/162] Add failing replay player mouse middle pause test --- .../Visual/Gameplay/TestSceneReplayPlayer.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs index e0a6a60ff2..601fe445f0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs @@ -4,6 +4,7 @@ #nullable disable using NUnit.Framework; +using osu.Framework.Screens; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osuTK.Input; @@ -23,14 +24,29 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player loaded", () => Player.IsLoaded); } - [Test] - public void TestPause() + [TestCase("space")] + [TestCase("mouse middle")] + public void TestPause(string action) { double? lastTime = null; AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0); - AddStep("Pause playback", () => InputManager.Key(Key.Space)); + AddStep("Pause playback", () => + { + switch (action) + { + case "space": + InputManager.Key(Key.Space); + break; + + case "mouse middle": + InputManager.Click(MouseButton.Middle); + break; + } + }); + + AddAssert("player not exited", () => Player.IsCurrentScreen()); AddUntilStep("Time stopped progressing", () => { From d79ee29f29c64a1922bf32c2f1de34be6b5e8e68 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 1 Jan 2023 18:00:39 -0800 Subject: [PATCH 010/162] Make replays pause with middle mouse button instead of exiting --- .../Screens/Play/HUD/HoldForMenuButton.cs | 19 ++++++++++++++++++- osu.Game/Screens/Play/Player.cs | 3 ++- osu.Game/Screens/Play/ReplayPlayer.cs | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index f902e0903d..0921a9f18a 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Play.HUD public readonly Bindable IsPaused = new Bindable(); + public readonly Bindable ReplayLoaded = new Bindable(); + private HoldButton button; public Action Action { get; set; } @@ -60,6 +62,7 @@ namespace osu.Game.Screens.Play.HUD HoverGained = () => text.FadeIn(500, Easing.OutQuint), HoverLost = () => text.FadeOut(500, Easing.OutQuint), IsPaused = { BindTarget = IsPaused }, + ReplayLoaded = { BindTarget = ReplayLoaded }, Action = () => Action(), } }; @@ -110,6 +113,8 @@ namespace osu.Game.Screens.Play.HUD public readonly Bindable IsPaused = new Bindable(); + public readonly Bindable ReplayLoaded = new Bindable(); + protected override bool AllowMultipleFires => true; public Action HoverGained; @@ -251,7 +256,14 @@ namespace osu.Game.Screens.Play.HUD switch (e.Action) { case GlobalAction.Back: - case GlobalAction.PauseGameplay: // in the future this behaviour will differ for replays etc. + if (!pendingAnimation) + BeginConfirm(); + return true; + + case GlobalAction.PauseGameplay: + // handled by replay player + if (ReplayLoaded.Value) return false; + if (!pendingAnimation) BeginConfirm(); return true; @@ -265,7 +277,12 @@ namespace osu.Game.Screens.Play.HUD switch (e.Action) { case GlobalAction.Back: + AbortConfirm(); + break; + case GlobalAction.PauseGameplay: + if (ReplayLoaded.Value) return; + AbortConfirm(); break; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 05133fba35..7fe81987c8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -433,7 +433,8 @@ namespace osu.Game.Screens.Play HoldToQuit = { Action = () => PerformExit(true), - IsPaused = { BindTarget = GameplayClockContainer.IsPaused } + IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, + ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded }, }, KeyCounter = { diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index c5ef6b1585..77ee7bc113 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -81,6 +81,7 @@ namespace osu.Game.Screens.Play return true; case GlobalAction.TogglePauseReplay: + case GlobalAction.PauseGameplay: if (GameplayClockContainer.IsPaused.Value) GameplayClockContainer.Start(); else From 93a57b6871a98d94f898cfa6afbf5504ab9932c9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 7 Jan 2023 11:28:09 -0800 Subject: [PATCH 011/162] Separate pausing test instead of using test cases --- .../Visual/Gameplay/TestSceneReplayPlayer.cs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs index 601fe445f0..334d01f915 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs @@ -24,28 +24,40 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player loaded", () => Player.IsLoaded); } - [TestCase("space")] - [TestCase("mouse middle")] - public void TestPause(string action) + [Test] + public void TestPauseViaSpace() { double? lastTime = null; AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0); - AddStep("Pause playback", () => - { - switch (action) - { - case "space": - InputManager.Key(Key.Space); - break; + AddStep("Pause playback with space", () => InputManager.Key(Key.Space)); - case "mouse middle": - InputManager.Click(MouseButton.Middle); - break; - } + AddAssert("player not exited", () => Player.IsCurrentScreen()); + + AddUntilStep("Time stopped progressing", () => + { + double current = Player.GameplayClockContainer.CurrentTime; + bool changed = lastTime != current; + lastTime = current; + + return !changed; }); + AddWaitStep("wait some", 10); + + AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime); + } + + [Test] + public void TestPauseViaMiddleMouse() + { + double? lastTime = null; + + AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0); + + AddStep("Pause playback with middle mouse", () => InputManager.Click(MouseButton.Middle)); + AddAssert("player not exited", () => Player.IsCurrentScreen()); AddUntilStep("Time stopped progressing", () => From 45bae5d42470b12cb4900ccb24f1cf79ad05204f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 5 Jan 2023 13:05:20 -0800 Subject: [PATCH 012/162] Add middle mouse to toggle pause replay instead of using pause gameplay keybinds --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 1 + osu.Game/Screens/Play/ReplayPlayer.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 07cef50dec..3826139716 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -113,6 +113,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay), + new KeyBinding(InputKey.MouseMiddle, GlobalAction.TogglePauseReplay), new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward), new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 77ee7bc113..c5ef6b1585 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -81,7 +81,6 @@ namespace osu.Game.Screens.Play return true; case GlobalAction.TogglePauseReplay: - case GlobalAction.PauseGameplay: if (GameplayClockContainer.IsPaused.Value) GameplayClockContainer.Start(); else From 87ee9ab813e023d0d51bea755ba3f5db16356353 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 12 Oct 2022 19:04:45 +0200 Subject: [PATCH 013/162] Added custom menu items in DrawableCarouselBeatmap --- .../Carousel/DrawableCarouselBeatmap.cs | 19 ++++++++----------- osu.Game/Screens/Select/PlaySongSelect.cs | 12 ++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 3 +++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 4e10961e55..4c8f986563 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -49,8 +49,9 @@ namespace osu.Game.Screens.Select.Carousel private Sprite background; - private Action startRequested; - private Action editRequested; + private MenuItem[] customMenuItems; + + private Action selectRequested; private Action hideRequested; private Triangles triangles; @@ -86,9 +87,8 @@ namespace osu.Game.Screens.Select.Carousel if (songSelect != null) { - startRequested = b => songSelect.FinaliseSelection(b); - if (songSelect.AllowEditing) - editRequested = songSelect.Edit; + customMenuItems = songSelect.CustomMenuItems.Select(f => f.Invoke(beatmapInfo)).ToArray(); + selectRequested = b => songSelect.FinaliseSelection(b); } if (manager != null) @@ -195,7 +195,7 @@ namespace osu.Game.Screens.Select.Carousel protected override bool OnClick(ClickEvent e) { if (Item.State.Value == CarouselItemState.Selected) - startRequested?.Invoke(beatmapInfo); + selectRequested?.Invoke(beatmapInfo); return base.OnClick(e); } @@ -229,11 +229,8 @@ namespace osu.Game.Screens.Select.Carousel { List items = new List(); - if (startRequested != null) - items.Add(new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested(beatmapInfo))); - - if (editRequested != null) - items.Add(new OsuMenuItem(CommonStrings.ButtonsEdit, MenuItemType.Standard, () => editRequested(beatmapInfo))); + if (customMenuItems != null) + items.AddRange(customMenuItems); if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID))); diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index f73cfe8d55..15356baf44 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -1,15 +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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -29,6 +34,13 @@ namespace osu.Game.Screens.Select public override bool AllowExternalScreenChange => true; + public override Func[] CustomMenuItems => + new Func[] + { + b => new OsuMenuItem("Play", MenuItemType.Highlighted, () => FinaliseSelection(b)), + b => new OsuMenuItem(CommonStrings.ButtonsEdit, MenuItemType.Standard, () => Edit(b)) + }; + protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap(); private PlayBeatmapDetailArea playBeatmapDetailArea = null!; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f4804c6a6c..38f4b8ef40 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -37,6 +37,7 @@ using osu.Game.Collections; using osu.Game.Graphics.UserInterface; using System.Diagnostics; using JetBrains.Annotations; +using osu.Framework.Graphics.UserInterface; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -84,6 +85,8 @@ namespace osu.Game.Screens.Select public bool BeatmapSetsLoaded => IsLoaded && Carousel?.BeatmapSetsLoaded == true; + public virtual Func[] CustomMenuItems => new Func[] { b => new OsuMenuItem(@"Select", MenuItemType.Highlighted, () => FinaliseSelection(b)) }; + [Resolved] private Bindable> selectedMods { get; set; } From 1c0a6505756ef2ace70ae5d24dbddca14ef961e1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 16 Jan 2023 00:17:26 +0100 Subject: [PATCH 014/162] Fix typo --- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 15356baf44..92a23048c7 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select { notifications?.Post(new SimpleNotification { - Text = "The current ruleset doesn't have an autoplay mod avalaible!" + Text = "The current ruleset doesn't have an autoplay mod available!" }); return false; } From 832d033777eeeffff5e419343416a876d4deb43a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 16 Jan 2023 01:09:04 +0100 Subject: [PATCH 015/162] Fix merge issues --- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 8 ++++---- osu.Game/Screens/Select/PlaySongSelect.cs | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 75c367acba..a831271bb4 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -47,10 +47,10 @@ namespace osu.Game.Screens.Select.Carousel private Sprite background = null!; - private MenuItem[] customMenuItems; + private MenuItem[]? customMenuItems; - private Action selectRequested; - private Action hideRequested; + private Action? selectRequested; + private Action? hideRequested; private Triangles triangles = null!; @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Select.Carousel protected override bool OnClick(ClickEvent e) { - if (Item.State.Value == CarouselItemState.Selected) + if (Item?.State.Value == CarouselItemState.Selected) selectRequested?.Invoke(beatmapInfo); return base.OnClick(e); diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index aebdfb8a04..c6c2b69fd8 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -39,7 +38,7 @@ namespace osu.Game.Screens.Select new Func[] { b => new OsuMenuItem("Play", MenuItemType.Highlighted, () => FinaliseSelection(b)), - b => new OsuMenuItem(CommonStrings.ButtonsEdit, MenuItemType.Standard, () => Edit(b)) + b => new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsEdit, MenuItemType.Standard, () => Edit(b)) }; protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap(); From e5eab72aeb493dd870c243c1d7fec184bc2f97e7 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 19 Jan 2023 00:37:36 +0900 Subject: [PATCH 016/162] add check for preview time setting --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 3 + .../Rulesets/Edit/Checks/CheckPreviewTime.cs | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index a89a0e76a9..5f5aba26bb 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Edit new CheckUnsnappedObjects(), new CheckConcurrentObjects(), new CheckZeroLengthObjects(), + + // Timing + new CheckPreviewTime(), }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.cs b/osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.cs new file mode 100644 index 0000000000..4fad8c0af6 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.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. + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckPreviewTime : ICheck + { + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Timing, "Check Preview Time Consistency"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplatePreviewTimeConflict(this), + new IssueTemplateHasNoPreviewTime(this), + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var diffList = context.Beatmap.BeatmapInfo.BeatmapSet?.Beatmaps ?? new List(); + int previewTime = context.Beatmap.BeatmapInfo.Metadata.PreviewTime; + + if (previewTime == -1) + { + yield return new IssueTemplateHasNoPreviewTime(this).Create(); + } + + foreach (var diff in diffList) + { + if (diff.Equals(context.Beatmap.BeatmapInfo)) + { + continue; + } + + if (diff.Metadata.PreviewTime != previewTime) + { + yield return new IssueTemplatePreviewTimeConflict(this).Create(diff.DifficultyName); + } + } + } + + public class IssueTemplatePreviewTimeConflict : IssueTemplate + { + public IssueTemplatePreviewTimeConflict(ICheck check) + : base(check, IssueType.Warning, "Audio preview time conflicts with {0} diff") + { + } + + public Issue Create(string diffName) => new Issue(this, diffName); + } + + public class IssueTemplateHasNoPreviewTime : IssueTemplate + { + public IssueTemplateHasNoPreviewTime(ICheck check) + : base(check, IssueType.Warning, "A preview point for this map is not set. Consider settings one from the Timing menu") + { + } + + public Issue Create() => new Issue(this); + } + } +} From 997a1a8b02d0b99ba77a4dbe629ea5f2d8f46a9f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 19 Jan 2023 01:00:02 +0900 Subject: [PATCH 017/162] add test for CheckPreviewTime --- .../Editing/Checks/CheckPreviewTimeTest.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs b/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs new file mode 100644 index 0000000000..59ff8d4923 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + public class CheckPreviewTimeTest + { + private CheckPreviewTime check = null!; + + private IBeatmap beatmap = null!; + + [SetUp] + public void Setup() + { + check = new CheckPreviewTime(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { PreviewTime = -1 }, + BeatmapSet = new BeatmapSetInfo(new List + { + new BeatmapInfo + { + DifficultyName = "Test1", + Metadata = new BeatmapMetadata { PreviewTime = 5 }, + }, + new BeatmapInfo + { + DifficultyName = "Test2", + Metadata = new BeatmapMetadata { PreviewTime = 10 }, + }, + }) + } + }; + } + + [Test] + public void TestPreviewTimeNotSet() + { + setNoPreviewTimeBeatmap(); + var content = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + + var issues = check.Run(content).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplateHasNoPreviewTime); + } + + [Test] + public void TestPreviewTimeconflict() + { + setPreviewTimeConflictBeatmap(); + + var content = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + + var issues = check.Run(content).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplatePreviewTimeConflict); + Assert.That(issues.Single().Arguments.Single().ToString() == "Test1"); + } + + private void setNoPreviewTimeBeatmap() + { + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { PreviewTime = -1 }, + } + }; + } + + private void setPreviewTimeConflictBeatmap() + { + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { PreviewTime = 10 }, + BeatmapSet = new BeatmapSetInfo(new List + { + new BeatmapInfo + { + DifficultyName = "Test1", + Metadata = new BeatmapMetadata { PreviewTime = 5 }, + }, + new BeatmapInfo + { + DifficultyName = "Test2", + Metadata = new BeatmapMetadata { PreviewTime = 10 }, + }, + }) + } + }; + } + } +} From a1b5c9d910593cff6d7b6c903eacf632a60ec0b0 Mon Sep 17 00:00:00 2001 From: naoey Date: Sun, 22 Jan 2023 01:10:14 +0900 Subject: [PATCH 018/162] Don't transfer MD5 hashes in collections when copying beatmaps Fixes #22306. Changes beatmap saving so that by default it does not transfer the hashes in collections, and only transfers them when saving the same difficulty in the editor. Issue seems to have been introduced in https://github.com/ppy/osu/pull/20641. --- .../Editing/TestSceneEditorBeatmapCreation.cs | 57 ++++++++++++++++++- osu.Game/Beatmaps/BeatmapManager.cs | 11 +++- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 3f89bf9e9c..5aa2dd2ebf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; @@ -42,6 +43,9 @@ namespace osu.Game.Tests.Visual.Editing [Resolved] private BeatmapManager beatmapManager { get; set; } = null!; + [Resolved] + private RealmAccess realm { get; set; } = null!; + private Guid currentBeatmapSetID => EditorBeatmap.BeatmapInfo.BeatmapSet?.ID ?? Guid.Empty; public override void SetUpSteps() @@ -224,7 +228,8 @@ namespace osu.Game.Tests.Visual.Editing return beatmap != null && beatmap.DifficultyName == secondDifficultyName && set != null - && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified)); + && set.PerformRead(s => + s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified)); }); } @@ -327,6 +332,56 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2)); } + [Test] + public void TestCopyDifficultyDoesNotChangeCollections() + { + string originalDifficultyName = Guid.NewGuid().ToString(); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = originalDifficultyName); + AddStep("save beatmap", () => Editor.Save()); + + string originalMd5 = string.Empty; + BeatmapCollection collection = null!; + + AddStep("setup a collection with original beatmap", () => + { + collection = new BeatmapCollection("test copy"); + collection.BeatmapMD5Hashes.Add(originalMd5 = EditorBeatmap.BeatmapInfo.MD5Hash); + + realm.Write(r => + { + r.Add(collection); + }); + }); + + AddAssert("collection contains original beatmap", () => + !string.IsNullOrEmpty(originalMd5) && collection.BeatmapMD5Hashes.Contains(originalMd5)); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick()); + + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != originalDifficultyName; + }); + + AddStep("save without changes", () => Editor.Save()); + + AddAssert("collection still points to old beatmap", () => !collection.BeatmapMD5Hashes.Contains(EditorBeatmap.BeatmapInfo.MD5Hash) + && collection.BeatmapMD5Hashes.Contains(originalMd5)); + + AddStep("clean up collection", () => + { + realm.Write(r => + { + r.Remove(collection); + }); + }); + } + [Test] public void TestCreateMultipleNewDifficultiesSucceeds() { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 34637501fa..db9c450bbe 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -186,7 +186,10 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin); + // make sure that collections don't get transferred when adding new difficulties to a set (that function + // was added for the scenario of saving the same difficulty), since this path is invoked from copying + // an existing difficulty as well. + Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, false); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); @@ -285,7 +288,8 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) + /// Whether to transfer the MD5 hashes in collections referencing this beatmap. + public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null, bool transferCollections = false) { var setInfo = beatmapInfo.BeatmapSet; Debug.Assert(setInfo != null); @@ -337,7 +341,8 @@ namespace osu.Game.Beatmaps setInfo.CopyChangesToRealm(liveBeatmapSet); - beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); + if (transferCollections) + beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); ProcessBeatmap?.Invoke((liveBeatmapSet, false)); }); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 74ea933255..7161f34df2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -429,7 +429,7 @@ namespace osu.Game.Screens.Edit try { // save the loaded beatmap's data stream. - beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); + beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin, true); } catch (Exception ex) { diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 6e2f1e99cd..e9d186fe35 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual => testBeatmapManager.TestBeatmap; } - public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null, bool transferCollections = false) { // don't actually care about saving for this context. } From d4e5d7a8735bcb41792f77edf5a2a4fbdcda5c0c Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Sat, 21 Jan 2023 22:29:28 +0100 Subject: [PATCH 019/162] Move fixed scroll speed change out of classic mod for taiko --- .../Mods/TaikoModClassic.cs | 20 ++----------------- .../UI/DrawableTaikoRuleset.cs | 14 ++++++++++++- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 3b3b3e606c..2ccdfd40e5 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.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.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -9,30 +8,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield + public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset { - private DrawableTaikoRuleset? drawableTaikoRuleset; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; + var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false; var playfield = (TaikoPlayfield)drawableRuleset.Playfield; playfield.ClassicHitTargetPosition.Value = true; } - - public void Update(Playfield playfield) - { - Debug.Assert(drawableTaikoRuleset != null); - - // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. - const float scroll_rate = 10; - - // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. - float ratio = drawableTaikoRuleset.DrawHeight / 480; - - drawableTaikoRuleset.TimeRange.Value = (playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate; - } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 146daa8c27..40203440c5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -43,7 +43,6 @@ namespace osu.Game.Rulesets.Taiko.UI : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; - TimeRange.Value = 7000; } [BackgroundDependencyLoader] @@ -60,6 +59,19 @@ namespace osu.Game.Rulesets.Taiko.UI KeyBindingInputManager.Add(new DrumTouchInputArea()); } + protected override void Update() + { + base.Update(); + + // Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. + const float scroll_rate = 10; + + // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. + float ratio = DrawHeight / 480; + + TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate; + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); From 1c1c9915bb259d0021cd3e597609f714004594f8 Mon Sep 17 00:00:00 2001 From: naoey Date: Sun, 22 Jan 2023 10:27:33 +0900 Subject: [PATCH 020/162] Split saving new and existing beatmaps into separate flows --- .../Beatmaps/WorkingBeatmapManagerTest.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 148 ++++++++++-------- osu.Game/Screens/Edit/Editor.cs | 5 +- osu.Game/Tests/Visual/EditorTestScene.cs | 7 +- 4 files changed, 91 insertions(+), 71 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs index 89b8c8927d..4cd866392b 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Beatmaps Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Contain(initialHash)); Assert.That(noNewCollection.BeatmapMD5Hashes, Does.Not.Contain(initialHash)); - beatmaps.Save(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + beatmaps.SaveExistingBeatmap(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); string finalHash = working.BeatmapInfo.MD5Hash; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index db9c450bbe..d644031fe1 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -186,10 +186,7 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - // make sure that collections don't get transferred when adding new difficulties to a set (that function - // was added for the scenario of saving the same difficulty), since this path is invoked from copying - // an existing difficulty as well. - Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, false); + SaveNewBeatmap(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); @@ -283,78 +280,30 @@ namespace osu.Game.Beatmaps public IWorkingBeatmap DefaultBeatmap => workingBeatmapCache.DefaultBeatmap; /// - /// Saves an file against a given . + /// Saves an existing file against a given , also transferring the beatmap + /// hashes in any collections referencing it. /// /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - /// Whether to transfer the MD5 hashes in collections referencing this beatmap. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null, bool transferCollections = false) + public virtual void SaveExistingBeatmap(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { - var setInfo = beatmapInfo.BeatmapSet; - Debug.Assert(setInfo != null); + string oldMd5Hash = beatmapInfo.MD5Hash; - // Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. - // This should hopefully be temporary, assuming said clone is eventually removed. + save(beatmapInfo, beatmapContent, beatmapSkin); - // Warning: The directionality here is important. Changes have to be copied *from* beatmapContent (which comes from editor and is being saved) - // *to* the beatmapInfo (which is a database model and needs to receive values without the taiko slider velocity multiplier for correct operation). - // CopyTo() will undo such adjustments, while CopyFrom() will not. - beatmapContent.Difficulty.CopyTo(beatmapInfo.Difficulty); + Realm.Write(r => beatmapInfo.TransferCollectionReferences(r, oldMd5Hash)); + } - // All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding. - beatmapContent.BeatmapInfo = beatmapInfo; - - using (var stream = new MemoryStream()) - { - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); - - stream.Seek(0, SeekOrigin.Begin); - - // AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity. - var existingFileInfo = beatmapInfo.Path != null ? setInfo.GetFile(beatmapInfo.Path) : null; - string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo); - - // ensure that two difficulties from the set don't point at the same beatmap file. - if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) - throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'."); - - if (existingFileInfo != null) - DeleteFile(setInfo, existingFileInfo); - - string oldMd5Hash = beatmapInfo.MD5Hash; - - beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); - beatmapInfo.Hash = stream.ComputeSHA2Hash(); - - beatmapInfo.LastLocalUpdate = DateTimeOffset.Now; - beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; - - AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); - - updateHashAndMarkDirty(setInfo); - - Realm.Write(r => - { - var liveBeatmapSet = r.Find(setInfo.ID); - - setInfo.CopyChangesToRealm(liveBeatmapSet); - - if (transferCollections) - beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); - - ProcessBeatmap?.Invoke((liveBeatmapSet, false)); - }); - } - - Debug.Assert(beatmapInfo.BeatmapSet != null); - - static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo) - { - var metadata = beatmapInfo.Metadata; - return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidFilename(); - } + /// + /// Saves a new file against a given . + /// + /// The to save the content against. The file referenced by will be replaced. + /// The content to write. + /// The beatmap content to write, null if to be omitted. + public virtual void SaveNewBeatmap(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) + { + save(beatmapInfo, beatmapContent, beatmapSkin); } public void DeleteAllVideos() @@ -465,6 +414,69 @@ namespace osu.Game.Beatmaps setInfo.Status = BeatmapOnlineStatus.LocallyModified; } + private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin) + { + var setInfo = beatmapInfo.BeatmapSet; + Debug.Assert(setInfo != null); + + // Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. + // This should hopefully be temporary, assuming said clone is eventually removed. + + // Warning: The directionality here is important. Changes have to be copied *from* beatmapContent (which comes from editor and is being saved) + // *to* the beatmapInfo (which is a database model and needs to receive values without the taiko slider velocity multiplier for correct operation). + // CopyTo() will undo such adjustments, while CopyFrom() will not. + beatmapContent.Difficulty.CopyTo(beatmapInfo.Difficulty); + + // All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding. + beatmapContent.BeatmapInfo = beatmapInfo; + + using (var stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); + + stream.Seek(0, SeekOrigin.Begin); + + // AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity. + var existingFileInfo = beatmapInfo.Path != null ? setInfo.GetFile(beatmapInfo.Path) : null; + string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo); + + // ensure that two difficulties from the set don't point at the same beatmap file. + if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) + throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'."); + + if (existingFileInfo != null) + DeleteFile(setInfo, existingFileInfo); + + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); + beatmapInfo.Hash = stream.ComputeSHA2Hash(); + + beatmapInfo.LastLocalUpdate = DateTimeOffset.Now; + beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; + + AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); + + updateHashAndMarkDirty(setInfo); + + Realm.Write(r => + { + var liveBeatmapSet = r.Find(setInfo.ID); + + setInfo.CopyChangesToRealm(liveBeatmapSet); + + ProcessBeatmap?.Invoke((liveBeatmapSet, false)); + }); + } + + Debug.Assert(beatmapInfo.BeatmapSet != null); + + static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo) + { + var metadata = beatmapInfo.Metadata; + return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidFilename(); + } + } + #region Implementation of ICanAcceptFiles public Task Import(params string[] paths) => beatmapImporter.Import(paths); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7161f34df2..915f3d4a2a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -429,7 +429,10 @@ namespace osu.Game.Screens.Edit try { // save the loaded beatmap's data stream. - beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin, true); + if (isNewBeatmap) + beatmapManager.SaveNewBeatmap(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); + else + beatmapManager.SaveExistingBeatmap(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); } catch (Exception ex) { diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index e9d186fe35..8834911ff5 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -178,7 +178,12 @@ namespace osu.Game.Tests.Visual => testBeatmapManager.TestBeatmap; } - public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null, bool transferCollections = false) + public override void SaveExistingBeatmap(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + { + // don't actually care about saving for this context. + } + + public override void SaveNewBeatmap(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { // don't actually care about saving for this context. } From f8537c1cbe98bc38f0423e8eabdc2332a21d22ab Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 22 Jan 2023 22:19:04 +0100 Subject: [PATCH 021/162] Delegate file deletion to `ImportTask` to allow overriding it --- osu.Game/Database/ImportTask.cs | 9 +++++++++ osu.Game/Database/RealmArchiveModelImporter.cs | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index e7f599d85f..def20bc1fb 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -51,6 +51,15 @@ namespace osu.Game.Database : getReaderFrom(Path); } + /// + /// Deletes the file that is encapsulated by this . + /// + public virtual void DeleteFile() + { + if (File.Exists(Path)) + File.Delete(Path); + } + /// /// Creates an from a stream. /// diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index db8861c281..9d06c14b4b 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -201,8 +201,8 @@ namespace osu.Game.Database // TODO: Add a check to prevent files from storage to be deleted. try { - if (import != null && File.Exists(task.Path) && ShouldDeleteArchive(task.Path)) - File.Delete(task.Path); + if (import != null && ShouldDeleteArchive(task.Path)) + task.DeleteFile(); } catch (Exception e) { From de21864bd133e1d63c534b4c9406d5948e7938da Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 22 Jan 2023 22:47:16 +0100 Subject: [PATCH 022/162] Move logic to `AndroidImportTask` and add delete mechanism --- osu.Android/AndroidImportTask.cs | 57 ++++++++++++++++++++++++++++++++ osu.Android/OsuGameActivity.cs | 28 ++++------------ 2 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 osu.Android/AndroidImportTask.cs diff --git a/osu.Android/AndroidImportTask.cs b/osu.Android/AndroidImportTask.cs new file mode 100644 index 0000000000..ec946f71f3 --- /dev/null +++ b/osu.Android/AndroidImportTask.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using System.Threading.Tasks; +using Android.Content; +using Android.Net; +using Android.Provider; +using osu.Game.Database; + +namespace osu.Android +{ + public class AndroidImportTask : ImportTask + { + private readonly ContentResolver contentResolver; + + private readonly Uri uri; + + private AndroidImportTask(Stream stream, string filename, ContentResolver contentResolver, Uri uri) + : base(stream, filename) + { + this.contentResolver = contentResolver; + this.uri = uri; + } + + public override void DeleteFile() + { + contentResolver.Delete(uri, null, null); + } + + public static async Task Create(ContentResolver contentResolver, Uri uri) + { + // there are more performant overloads of this method, but this one is the most backwards-compatible + // (dates back to API 1). + + var cursor = contentResolver.Query(uri, null, null, null, null); + + if (cursor == null) + return null; + + cursor.MoveToFirst(); + + int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName); + string filename = cursor.GetString(filenameColumn); + + // SharpCompress requires archive streams to be seekable, which the stream opened by + // OpenInputStream() seems to not necessarily be. + // copy to an arbitrary-access memory stream to be able to proceed with the import. + var copy = new MemoryStream(); + + using (var stream = contentResolver.OpenInputStream(uri)) + await stream.CopyToAsync(copy).ConfigureAwait(false); + + return new AndroidImportTask(copy, filename, contentResolver, uri); + } + } +} diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index ca3d628447..f0a6e4733c 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -14,7 +13,6 @@ using Android.Content; using Android.Content.PM; using Android.Graphics; using Android.OS; -using Android.Provider; using Android.Views; using osu.Framework.Android; using osu.Game.Database; @@ -131,28 +129,14 @@ namespace osu.Android await Task.WhenAll(uris.Select(async uri => { - // there are more performant overloads of this method, but this one is the most backwards-compatible - // (dates back to API 1). - var cursor = ContentResolver?.Query(uri, null, null, null, null); + var task = await AndroidImportTask.Create(ContentResolver!, uri).ConfigureAwait(false); - if (cursor == null) - return; - - cursor.MoveToFirst(); - - int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName); - string filename = cursor.GetString(filenameColumn); - - // SharpCompress requires archive streams to be seekable, which the stream opened by - // OpenInputStream() seems to not necessarily be. - // copy to an arbitrary-access memory stream to be able to proceed with the import. - var copy = new MemoryStream(); - using (var stream = ContentResolver.OpenInputStream(uri)) - await stream.CopyToAsync(copy).ConfigureAwait(false); - - lock (tasks) + if (task != null) { - tasks.Add(new ImportTask(copy, filename)); + lock (tasks) + { + tasks.Add(task); + } } })).ConfigureAwait(false); From e16105d0593411dbb84e9b0cc5949fbb8d9e1737 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 22 Jan 2023 23:11:50 +0100 Subject: [PATCH 023/162] Fix NRT and add more safety --- osu.Android/AndroidImportTask.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Android/AndroidImportTask.cs b/osu.Android/AndroidImportTask.cs index ec946f71f3..7273a6da5c 100644 --- a/osu.Android/AndroidImportTask.cs +++ b/osu.Android/AndroidImportTask.cs @@ -38,10 +38,11 @@ namespace osu.Android if (cursor == null) return null; - cursor.MoveToFirst(); + if (!cursor.MoveToFirst()) + return null; int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName); - string filename = cursor.GetString(filenameColumn); + string filename = cursor.GetString(filenameColumn) ?? uri.Path ?? string.Empty; // SharpCompress requires archive streams to be seekable, which the stream opened by // OpenInputStream() seems to not necessarily be. @@ -49,7 +50,12 @@ namespace osu.Android var copy = new MemoryStream(); using (var stream = contentResolver.OpenInputStream(uri)) + { + if (stream == null) + return null; + await stream.CopyToAsync(copy).ConfigureAwait(false); + } return new AndroidImportTask(copy, filename, contentResolver, uri); } From 5afb733fb2e8dbf4350880699350be590bd726f6 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 23 Jan 2023 15:26:28 +0900 Subject: [PATCH 024/162] change IssueTemplatePreviewTimeConflict's text --- osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs b/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs index 59ff8d4923..2b7d37dc81 100644 --- a/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Editing.Checks Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplatePreviewTimeConflict); - Assert.That(issues.Single().Arguments.Single().ToString() == "Test1"); + Assert.That(issues.Single().Arguments.FirstOrDefault()?.ToString() == "Test1"); } private void setNoPreviewTimeBeatmap() diff --git a/osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.cs b/osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.cs index 4fad8c0af6..05e49eba84 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckPreviewTime.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (diff.Metadata.PreviewTime != previewTime) { - yield return new IssueTemplatePreviewTimeConflict(this).Create(diff.DifficultyName); + yield return new IssueTemplatePreviewTimeConflict(this).Create(diff.DifficultyName, previewTime, diff.Metadata.PreviewTime); } } } @@ -44,11 +44,15 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplatePreviewTimeConflict : IssueTemplate { public IssueTemplatePreviewTimeConflict(ICheck check) - : base(check, IssueType.Warning, "Audio preview time conflicts with {0} diff") + : base(check, IssueType.Warning, "Audio preview time {1} doesn't match \"{0}\"'s preview time {2}") { } - public Issue Create(string diffName) => new Issue(this, diffName); + public Issue Create(string diffName, int originalTime, int conflictTime) => + // preview time should show (not set) when it is not set. + new Issue(this, diffName, + originalTime != -1 ? $"({originalTime:N0})" : "(not set)", + conflictTime != -1 ? $"({conflictTime:N0})" : "(not set)"); } public class IssueTemplateHasNoPreviewTime : IssueTemplate From 338a14ff69c9dcda990b2c714c2831028f8ed1cf Mon Sep 17 00:00:00 2001 From: itsMapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Mon, 23 Jan 2023 01:01:45 -0600 Subject: [PATCH 025/162] new mania hold tail --- .../Skinning/Argon/ArgonHoldNoteTailPiece.cs | 76 +++++++++++-------- .../Skinning/Argon/ArgonNotePiece.cs | 4 +- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs index a2166a6708..6f6270c1f5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs @@ -3,13 +3,13 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -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.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon @@ -19,45 +19,57 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private readonly IBindable direction = new Bindable(); private readonly IBindable accentColour = new Bindable(); - private readonly Box colouredBox; - private readonly Box shadow; + private readonly Container spriteContainer; + private readonly Container shadeContainer; + private readonly Circle hitLine; public ArgonHoldNoteTailPiece() { RelativeSizeAxes = Axes.X; - Height = ArgonNotePiece.NOTE_HEIGHT; + + // multiply by two so that the hold body extends up to the height of the note head accent + Height = ArgonNotePiece.NOTE_HEIGHT * ArgonNotePiece.NOTE_ACCENT_RATIO * 2; CornerRadius = ArgonNotePiece.CORNER_RADIUS; Masking = true; InternalChildren = new Drawable[] { - shadow = new Box - { + shadeContainer = new Container { RelativeSizeAxes = Axes.Both, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Height = 0.82f, - Masking = true, + Height = 0.5f, CornerRadius = ArgonNotePiece.CORNER_RADIUS, - Children = new Drawable[] - { - colouredBox = new Box + Masking = true, + Children = new Drawable[] { + new Box { RelativeSizeAxes = Axes.Both, - } - } + Colour = Color4.Black, + Alpha = 0.2f, + }, + }, }, - new Circle - { + spriteContainer = new Container { + RelativeSizeAxes = Axes.X, + Height = ArgonNotePiece.NOTE_HEIGHT, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 4, + Icon = FontAwesome.Solid.AngleDown, + Size = new Vector2(20), + Scale = new Vector2(1, 0.7f), + Colour = Color4.White, + Alpha = 0.2f, + }, + }, + }, + hitLine = new Circle { RelativeSizeAxes = Axes.X, Height = ArgonNotePiece.CORNER_RADIUS * 2, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, }, }; } @@ -77,19 +89,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private void onDirectionChanged(ValueChangedEvent direction) { - colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up - ? Anchor.TopCentre - : Anchor.BottomCentre; + hitLine.Anchor = hitLine.Origin = + spriteContainer.Anchor = spriteContainer.Origin = + shadeContainer.Anchor = shadeContainer.Origin = + direction.NewValue == ScrollingDirection.Up + ? Anchor.TopCentre + : Anchor.BottomCentre; } private void onAccentChanged(ValueChangedEvent accent) { - colouredBox.Colour = ColourInfo.GradientVertical( - accent.NewValue, - accent.NewValue.Darken(0.1f) - ); - - shadow.Colour = accent.NewValue.Darken(0.5f); + hitLine.Colour = accent.NewValue; } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs index 25b1965c18..f680483634 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon internal partial class ArgonNotePiece : CompositeDrawable { public const float NOTE_HEIGHT = 42; - + public const float NOTE_ACCENT_RATIO = 0.82f; public const float CORNER_RADIUS = 3.4f; private readonly IBindable direction = new Bindable(); @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, - Height = 0.82f, + Height = NOTE_ACCENT_RATIO, Masking = true, CornerRadius = CORNER_RADIUS, Children = new Drawable[] From 9a053f114ebc0040d850f94e5b62f41f0f15c587 Mon Sep 17 00:00:00 2001 From: itsMapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Mon, 23 Jan 2023 01:50:50 -0600 Subject: [PATCH 026/162] remove sprite --- .../Skinning/Argon/ArgonHoldNoteTailPiece.cs | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs index 6f6270c1f5..02325904c4 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs @@ -6,10 +6,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon @@ -19,7 +17,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private readonly IBindable direction = new Bindable(); private readonly IBindable accentColour = new Bindable(); - private readonly Container spriteContainer; private readonly Container shadeContainer; private readonly Circle hitLine; @@ -28,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon RelativeSizeAxes = Axes.X; // multiply by two so that the hold body extends up to the height of the note head accent - Height = ArgonNotePiece.NOTE_HEIGHT * ArgonNotePiece.NOTE_ACCENT_RATIO * 2; + Height = ArgonNotePiece.NOTE_HEIGHT * 2; CornerRadius = ArgonNotePiece.CORNER_RADIUS; Masking = true; @@ -45,25 +42,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, - Alpha = 0.2f, - }, - }, - }, - spriteContainer = new Container { - RelativeSizeAxes = Axes.X, - Height = ArgonNotePiece.NOTE_HEIGHT, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = 4, - Icon = FontAwesome.Solid.AngleDown, - Size = new Vector2(20), - Scale = new Vector2(1, 0.7f), - Colour = Color4.White, - Alpha = 0.2f, + Alpha = 0.4f, }, }, }, @@ -90,7 +69,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private void onDirectionChanged(ValueChangedEvent direction) { hitLine.Anchor = hitLine.Origin = - spriteContainer.Anchor = spriteContainer.Origin = shadeContainer.Anchor = shadeContainer.Origin = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre From c4d5957ac3238009529833229ee63ac16377b1a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 17:07:27 +0900 Subject: [PATCH 027/162] Add empty space tap-streaming support for osu! ruleset on touchscreen devices --- .../TestSceneTouchInput.cs | 147 +++++++++++++++++- osu.Game.Rulesets.Osu/OsuInputManager.cs | 5 + .../UI/OsuTouchInputMapper.cs | 40 ++++- 3 files changed, 186 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs index 10d0143351..5530bd4b99 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs @@ -13,7 +13,12 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; @@ -33,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Tests private OsuInputManager osuInputManager = null!; + private Container mainContent = null!; + [SetUpSteps] public void SetUpSteps() { @@ -44,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests { osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo) { - Child = new Container + Child = mainContent = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -54,12 +61,14 @@ namespace osu.Game.Rulesets.Osu.Tests { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, + Depth = float.MinValue, X = -100, }, rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton) { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, + Depth = float.MinValue, X = 100, } }, @@ -116,9 +125,127 @@ namespace osu.Game.Rulesets.Osu.Tests endTouch(TouchSource.Touch2); checkPosition(TouchSource.Touch2); - // note that touch1 was never ended, but becomes active for tracking again. + // note that touch1 was never ended, but is no longer valid for touch input due to touch 2 occurring. beginTouch(TouchSource.Touch1); + checkPosition(TouchSource.Touch2); + } + + [Test] + public void TestStreamInput() + { + // In this scenario, the user is tapping on the first object in a stream, + // then using one or two fingers in empty space to continue the stream. + + addHitCircleAt(TouchSource.Touch1); + beginTouch(TouchSource.Touch1); + + // The first touch is handled as normal. + assertKeyCounter(1, 0); + checkPressed(OsuAction.LeftButton); checkPosition(TouchSource.Touch1); + + // The second touch should release the first, and also act as a right button. + beginTouch(TouchSource.Touch2); + + assertKeyCounter(1, 1); + // Importantly, this is different from the simple case because an object was interacted with + // in the first touch, but not the second touch. + checkNotPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + // Also importantly, the positional part of the second touch is ignored. + checkPosition(TouchSource.Touch1); + + // In this scenario, a third touch should be allowed, and handled similarly to the second. + beginTouch(TouchSource.Touch3); + + assertKeyCounter(2, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + // Position is still ignored. + checkPosition(TouchSource.Touch1); + + endTouch(TouchSource.Touch2); + + checkPressed(OsuAction.LeftButton); + checkNotPressed(OsuAction.RightButton); + // Position is still ignored. + checkPosition(TouchSource.Touch1); + + // User continues streaming + beginTouch(TouchSource.Touch2); + + assertKeyCounter(2, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + // Position is still ignored. + checkPosition(TouchSource.Touch1); + + // In this mode a maximum of three touches should be supported. + // A fourth touch should result in no changes anywhere. + beginTouch(TouchSource.Touch4); + assertKeyCounter(2, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch1); + endTouch(TouchSource.Touch4); + } + + [Test] + public void TestNonStreamOverlappingDirectTouchesWithRelease() + { + // In this scenario, the user is tapping on three circles directly while correctly releasing the first touch. + // All three should be recognised. + + addHitCircleAt(TouchSource.Touch1); + addHitCircleAt(TouchSource.Touch2); + addHitCircleAt(TouchSource.Touch3); + + beginTouch(TouchSource.Touch1); + assertKeyCounter(1, 0); + checkPressed(OsuAction.LeftButton); + checkPosition(TouchSource.Touch1); + + beginTouch(TouchSource.Touch2); + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + endTouch(TouchSource.Touch1); + + beginTouch(TouchSource.Touch3); + assertKeyCounter(2, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch3); + } + + [Test] + public void TestNonStreamOverlappingDirectTouchesWithoutRelease() + { + // In this scenario, the user is tapping on three circles directly without releasing any touches. + // The first two should be recognised, but a third should not (as the user already has two fingers down). + + addHitCircleAt(TouchSource.Touch1); + addHitCircleAt(TouchSource.Touch2); + addHitCircleAt(TouchSource.Touch3); + + beginTouch(TouchSource.Touch1); + assertKeyCounter(1, 0); + checkPressed(OsuAction.LeftButton); + checkPosition(TouchSource.Touch1); + + beginTouch(TouchSource.Touch2); + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + beginTouch(TouchSource.Touch3); + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch3); } [Test] @@ -263,6 +390,22 @@ namespace osu.Game.Rulesets.Osu.Tests assertKeyCounter(1, 1); } + private void addHitCircleAt(TouchSource source) + { + AddStep($"Add circle at {source}", () => + { + var hitCircle = new HitCircle(); + + hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + mainContent.Add(new DrawableHitCircle(hitCircle) + { + Clock = new FramedClock(new ManualClock()), + Position = mainContent.ToLocalSpace(getSanePositionForSource(source)), + }); + }); + } + private void beginTouch(TouchSource source, Vector2? screenSpacePosition = null) => AddStep($"Begin touch for {source}", () => InputManager.BeginTouch(new Touch(source, screenSpacePosition ??= getSanePositionForSource(source)))); diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 99ae706369..465dd13362 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -9,8 +9,10 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Osu { @@ -40,6 +42,9 @@ namespace osu.Game.Rulesets.Osu protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new OsuKeyBindingContainer(ruleset, variant, unique); + public bool CheckScreenSpaceActionPressJudgeable(Vector2 screenSpacePosition) => + NonPositionalInputQueue.OfType().Any(c => c.ReceivePositionalInputAt(screenSpacePosition)); + public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) { diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index c75e179443..9842e24d05 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -44,6 +44,8 @@ namespace osu.Game.Rulesets.Osu.UI handleTouchMovement(e); } + private TrackedTouch? positionTrackingTouch; + protected override bool OnTouchDown(TouchDownEvent e) { OsuAction action = trackedTouches.Any(t => t.Action == OsuAction.LeftButton) @@ -53,7 +55,31 @@ namespace osu.Game.Rulesets.Osu.UI // Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future. bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !mouseDisabled.Value && trackedTouches.All(t => t.Action != action); - trackedTouches.Add(new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null)); + // If we can actually accept as an action, check whether this tap was on a circle's receptor. + // This case gets special handling to allow for empty-space stream tapping. + bool isDirectCircleTouch = osuInputManager.CheckScreenSpaceActionPressJudgeable(e.ScreenSpaceTouchDownPosition); + + var trackedTouch = new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null, isDirectCircleTouch); + + if (isDirectCircleTouch) + positionTrackingTouch = trackedTouch; + else + { + // If no direct touch is registered, we always use the new (latest) touch for positional tracking. + if (positionTrackingTouch?.DirectTouch != true) + positionTrackingTouch = trackedTouch; + else + { + // If not a direct circle touch, consider whether to release the an original direct touch. + if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction) + { + osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction); + positionTrackingTouch.Action = null; + } + } + } + + trackedTouches.Add(trackedTouch); // Important to update position before triggering the pressed action. handleTouchMovement(e); @@ -67,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.UI private void handleTouchMovement(TouchEvent touchEvent) { // Movement should only be tracked for the most recent touch. - if (touchEvent.Touch.Source != trackedTouches.Last().Source) + if (touchEvent.Touch.Source != positionTrackingTouch?.Source) return; if (!osuInputManager.AllowUserCursorMovement) @@ -83,6 +109,9 @@ namespace osu.Game.Rulesets.Osu.UI if (tracked.Action is OsuAction action) osuInputManager.KeyBindingContainer.TriggerReleased(action); + if (positionTrackingTouch == tracked) + positionTrackingTouch = null; + trackedTouches.Remove(tracked); base.OnTouchUp(e); @@ -92,12 +121,15 @@ namespace osu.Game.Rulesets.Osu.UI { public readonly TouchSource Source; - public readonly OsuAction? Action; + public OsuAction? Action; - public TrackedTouch(TouchSource source, OsuAction? action) + public readonly bool DirectTouch; + + public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch) { Source = source; Action = action; + DirectTouch = directTouch; } } } From 238a3833e23ba829d913e73872ac6aa1b1883e80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 17:28:01 +0900 Subject: [PATCH 028/162] Add test coverage of stream scenario with an initial finger down --- .../TestSceneTouchInput.cs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs index 5530bd4b99..4d119bc2ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs @@ -148,8 +148,8 @@ namespace osu.Game.Rulesets.Osu.Tests beginTouch(TouchSource.Touch2); assertKeyCounter(1, 1); - // Importantly, this is different from the simple case because an object was interacted with - // in the first touch, but not the second touch. + // Importantly, this is different from the simple case because an object was interacted with in the first touch, but not the second touch. + // left button is automatically released. checkNotPressed(OsuAction.LeftButton); checkPressed(OsuAction.RightButton); // Also importantly, the positional part of the second touch is ignored. @@ -190,6 +190,52 @@ namespace osu.Game.Rulesets.Osu.Tests endTouch(TouchSource.Touch4); } + [Test] + public void TestStreamInputWithInitialTouchDown() + { + // In this scenario, the user is wanting to use stream input but we start with one finger still on the screen. + + addHitCircleAt(TouchSource.Touch2); + + beginTouch(TouchSource.Touch1); + assertKeyCounter(1, 0); + checkPressed(OsuAction.LeftButton); + checkPosition(TouchSource.Touch1); + + // hits circle + beginTouch(TouchSource.Touch2); + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + endTouch(TouchSource.Touch1); + checkNotPressed(OsuAction.LeftButton); + + // stream using other two fingers while touch2 tracks + beginTouch(TouchSource.Touch1); + assertKeyCounter(2, 1); + checkPressed(OsuAction.LeftButton); + // right button is automatically released + checkNotPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + beginTouch(TouchSource.Touch3); + assertKeyCounter(2, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + endTouch(TouchSource.Touch1); + checkNotPressed(OsuAction.LeftButton); + + beginTouch(TouchSource.Touch1); + assertKeyCounter(3, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + } + [Test] public void TestNonStreamOverlappingDirectTouchesWithRelease() { From b436b7b99b475348adf7914d27f1e801275c47f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 17:39:41 +0900 Subject: [PATCH 029/162] Add test coverage of more streaming scenarios --- .../TestSceneTouchInput.cs | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs index 4d119bc2ea..22707d218c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs @@ -191,9 +191,10 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestStreamInputWithInitialTouchDown() + public void TestStreamInputWithInitialTouchDownLeft() { // In this scenario, the user is wanting to use stream input but we start with one finger still on the screen. + // That finger is mapped to a left action. addHitCircleAt(TouchSource.Touch2); @@ -202,7 +203,7 @@ namespace osu.Game.Rulesets.Osu.Tests checkPressed(OsuAction.LeftButton); checkPosition(TouchSource.Touch1); - // hits circle + // hits circle as right action beginTouch(TouchSource.Touch2); assertKeyCounter(1, 1); checkPressed(OsuAction.LeftButton); @@ -236,6 +237,56 @@ namespace osu.Game.Rulesets.Osu.Tests checkPosition(TouchSource.Touch2); } + [Test] + public void TestStreamInputWithInitialTouchDownRight() + { + // In this scenario, the user is wanting to use stream input but we start with one finger still on the screen. + // That finger is mapped to a right action. + + beginTouch(TouchSource.Touch1); + beginTouch(TouchSource.Touch2); + + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + + endTouch(TouchSource.Touch1); + + addHitCircleAt(TouchSource.Touch1); + + // hits circle as left action + beginTouch(TouchSource.Touch1); + assertKeyCounter(2, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch1); + + endTouch(TouchSource.Touch2); + + // stream using other two fingers while touch1 tracks + beginTouch(TouchSource.Touch2); + assertKeyCounter(2, 2); + checkPressed(OsuAction.RightButton); + // left button is automatically released + checkNotPressed(OsuAction.LeftButton); + checkPosition(TouchSource.Touch1); + + beginTouch(TouchSource.Touch3); + assertKeyCounter(3, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch1); + + endTouch(TouchSource.Touch2); + checkNotPressed(OsuAction.RightButton); + + beginTouch(TouchSource.Touch2); + assertKeyCounter(3, 3); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch1); + } + [Test] public void TestNonStreamOverlappingDirectTouchesWithRelease() { From b9ed6a7a7c97d014f08b3d39c0906d78f640708f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 17:54:17 +0900 Subject: [PATCH 030/162] Add visual demonstration of streaming that runs a bit faster than other tests --- .../TestSceneTouchInput.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs index 22707d218c..4589eb88c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs @@ -14,11 +14,13 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Testing; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; @@ -70,6 +72,10 @@ namespace osu.Game.Rulesets.Osu.Tests Origin = Anchor.CentreLeft, Depth = float.MinValue, X = 100, + }, + new OsuCursorContainer + { + Depth = float.MinValue, } }, } @@ -79,6 +85,40 @@ namespace osu.Game.Rulesets.Osu.Tests }); } + [Test] + public void TestStreamInputVisual() + { + addHitCircleAt(TouchSource.Touch1); + addHitCircleAt(TouchSource.Touch2); + + beginTouch(TouchSource.Touch1); + beginTouch(TouchSource.Touch2); + + endTouch(TouchSource.Touch1); + + int i = 0; + + AddRepeatStep("Alternate", () => + { + TouchSource down = i % 2 == 0 ? TouchSource.Touch3 : TouchSource.Touch4; + TouchSource up = i % 2 == 0 ? TouchSource.Touch4 : TouchSource.Touch3; + + // sometimes the user will end the previous touch before touching again, sometimes not. + if (RNG.NextBool()) + { + InputManager.BeginTouch(new Touch(down, getSanePositionForSource(down))); + InputManager.EndTouch(new Touch(up, getSanePositionForSource(up))); + } + else + { + InputManager.EndTouch(new Touch(up, getSanePositionForSource(up))); + InputManager.BeginTouch(new Touch(down, getSanePositionForSource(down))); + } + + i++; + }, 100); + } + [Test] public void TestSimpleInput() { From 6daa36477942d6179683ec59387ae898f0e0936b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Mon, 23 Jan 2023 13:53:31 +0100 Subject: [PATCH 031/162] adding setting to adjust blur of the background of the song select screen --- osu.Game/Configuration/OsuConfigManager.cs | 3 ++ osu.Game/Localisation/UserInterfaceStrings.cs | 6 ++++ .../UserInterface/SongSelectSettings.cs | 5 +++ osu.Game/Screens/Select/SongSelect.cs | 33 +++++++++++++++++-- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6cbb677a64..2b7c2102d4 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,6 +60,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); + SetDefault(OsuSetting.BeatmapSelectionBlurLevel, 1f, 0, 1f, 0.01f); + // Online settings SetDefault(OsuSetting.Username, string.Empty); SetDefault(OsuSetting.Token, string.Empty); @@ -339,6 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, + BeatmapSelectionBlurLevel, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index ea664d7b50..0bf7dae403 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -109,6 +109,12 @@ namespace osu.Game.Localisation /// public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); + /// + /// "Beatmap selection blur level" + /// + public static LocalisableString BeatmapSelectionBlurLevel => new TranslatableString(getKey(@"beatmap_selection_blur_level"), @"Beatmap selection blur level"); + + /// /// "no limit" /// diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 8b5e0b75b7..828119981f 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -42,6 +42,11 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.ModSelectHotkeyStyle, Current = config.GetBindable(OsuSetting.ModSelectHotkeyStyle), ClassicDefault = ModSelectHotkeyStyle.Classic + }, + new SettingsSlider + { + LabelText = UserInterfaceStrings.BeatmapSelectionBlurLevel, + Current = config.GetBindable(OsuSetting.BeatmapSelectionBlurLevel) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4b3222c14a..bd1498f0dd 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -35,6 +35,7 @@ using osu.Game.Collections; using osu.Game.Graphics.UserInterface; using System.Diagnostics; using osu.Framework.Extensions.ObjectExtensions; +using osu.Game.Configuration; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -124,9 +125,26 @@ namespace osu.Game.Screens.Select [Resolved] internal IOverlayManager? OverlayManager { get; private set; } - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender) + private Bindable backgroundBlurLevel { get; set; } = new BindableFloat(); + + private void applyBackgroundBlur(float v) { + ApplyToBackground(background => + { + background.IgnoreUserSettings.Value = true; + background.BlurAmount.Value = v * BACKGROUND_BLUR; + }); + } + private void applyBackgroundBlur(ValueChangedEvent v) + { + applyBackgroundBlur(v.NewValue); + } + + [BackgroundDependencyLoader(true)] + private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) + { + backgroundBlurLevel = config.GetBindable(OsuSetting.BeatmapSelectionBlurLevel); + LoadComponentAsync(Carousel = new BeatmapCarousel { AllowSelection = false, // delay any selection until our bindables are ready to make a good choice. @@ -549,6 +567,9 @@ namespace osu.Game.Screens.Select { base.OnEntering(e); + backgroundBlurLevel.ValueChanged += applyBackgroundBlur; + applyBackgroundBlur(backgroundBlurLevel.Value); + this.FadeInFromZero(250); FilterControl.Activate(); @@ -596,6 +617,8 @@ namespace osu.Game.Screens.Select public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); + backgroundBlurLevel.ValueChanged += applyBackgroundBlur; + applyBackgroundBlur(backgroundBlurLevel.Value); // required due to https://github.com/ppy/osu-framework/issues/3218 ModSelect.SelectedMods.Disabled = false; @@ -641,6 +664,8 @@ namespace osu.Game.Screens.Select // Without this, it's possible for a transfer to happen while we are not the current screen. transferRulesetValue(); + backgroundBlurLevel.ValueChanged -= applyBackgroundBlur; + ModSelect.SelectedMods.UnbindFrom(selectedMods); playExitingTransition(); @@ -649,6 +674,8 @@ namespace osu.Game.Screens.Select public override bool OnExiting(ScreenExitEvent e) { + backgroundBlurLevel.ValueChanged -= applyBackgroundBlur; + if (base.OnExiting(e)) return true; @@ -742,7 +769,7 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = BACKGROUND_BLUR; + backgroundModeBeatmap.BlurAmount.Value = backgroundBlurLevel.Value * BACKGROUND_BLUR; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); From f13a5465ba38e91be20e4cdb3d811398e01b8430 Mon Sep 17 00:00:00 2001 From: Jeremiah DECOMBE Date: Mon, 23 Jan 2023 23:07:50 +0100 Subject: [PATCH 032/162] variable naming and loc modifications --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/UserInterfaceStrings.cs | 5 ++--- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 2b7c2102d4..f7b84527b5 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); - SetDefault(OsuSetting.BeatmapSelectionBlurLevel, 1f, 0, 1f, 0.01f); + SetDefault(OsuSetting.BeatmapSelectionBackgoundBlurLevel, 1f, 0, 1f, 0.01f); // Online settings SetDefault(OsuSetting.Username, string.Empty); @@ -341,7 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, - BeatmapSelectionBlurLevel, + BeatmapSelectionBackgoundBlurLevel, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 0bf7dae403..9abb3aaddc 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -110,10 +110,9 @@ namespace osu.Game.Localisation public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); /// - /// "Beatmap selection blur level" + /// "Song select background blur" /// - public static LocalisableString BeatmapSelectionBlurLevel => new TranslatableString(getKey(@"beatmap_selection_blur_level"), @"Beatmap selection blur level"); - + public static LocalisableString BeatmapSelectionBackgroundBlurLevel => new TranslatableString(getKey(@"beatmap_selection_background_blur_level"), @"Song select background blur"); /// /// "no limit" diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 828119981f..f0b2721153 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -45,8 +45,8 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsSlider { - LabelText = UserInterfaceStrings.BeatmapSelectionBlurLevel, - Current = config.GetBindable(OsuSetting.BeatmapSelectionBlurLevel) + LabelText = UserInterfaceStrings.BeatmapSelectionBackgroundBlurLevel, + Current = config.GetBindable(OsuSetting.BeatmapSelectionBackgoundBlurLevel) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bd1498f0dd..bf6e433a8e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - backgroundBlurLevel = config.GetBindable(OsuSetting.BeatmapSelectionBlurLevel); + backgroundBlurLevel = config.GetBindable(OsuSetting.BeatmapSelectionBackgoundBlurLevel); LoadComponentAsync(Carousel = new BeatmapCarousel { From 26adc28943a802ab924e21f3da02b7a46db083e9 Mon Sep 17 00:00:00 2001 From: Jeremiah DECOMBE Date: Mon, 23 Jan 2023 23:15:37 +0100 Subject: [PATCH 033/162] missing blank line between methods --- osu.Game/Screens/Select/SongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bf6e433a8e..c43344d963 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -135,6 +135,7 @@ namespace osu.Game.Screens.Select background.BlurAmount.Value = v * BACKGROUND_BLUR; }); } + private void applyBackgroundBlur(ValueChangedEvent v) { applyBackgroundBlur(v.NewValue); From e6de167adb1c549d060314786730bfef06a243f5 Mon Sep 17 00:00:00 2001 From: naoey Date: Tue, 24 Jan 2023 07:25:13 +0900 Subject: [PATCH 034/162] Revert split and make collections boolean internal to `BeatmapManager` --- .../Beatmaps/WorkingBeatmapManagerTest.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 26 ++++++------------- osu.Game/Screens/Edit/Editor.cs | 5 +--- osu.Game/Tests/Visual/EditorTestScene.cs | 7 +---- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs index 4cd866392b..89b8c8927d 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Beatmaps Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Contain(initialHash)); Assert.That(noNewCollection.BeatmapMD5Hashes, Does.Not.Contain(initialHash)); - beatmaps.SaveExistingBeatmap(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + beatmaps.Save(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); string finalHash = working.BeatmapInfo.MD5Hash; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d644031fe1..fc6d4081e7 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - SaveNewBeatmap(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin); + save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, false); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); @@ -286,22 +286,7 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void SaveExistingBeatmap(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) - { - string oldMd5Hash = beatmapInfo.MD5Hash; - - save(beatmapInfo, beatmapContent, beatmapSkin); - - Realm.Write(r => beatmapInfo.TransferCollectionReferences(r, oldMd5Hash)); - } - - /// - /// Saves a new file against a given . - /// - /// The to save the content against. The file referenced by will be replaced. - /// The content to write. - /// The beatmap content to write, null if to be omitted. - public virtual void SaveNewBeatmap(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) + public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { save(beatmapInfo, beatmapContent, beatmapSkin); } @@ -414,7 +399,7 @@ namespace osu.Game.Beatmaps setInfo.Status = BeatmapOnlineStatus.LocallyModified; } - private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin) + private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin, bool transferCollections = true) { var setInfo = beatmapInfo.BeatmapSet; Debug.Assert(setInfo != null); @@ -448,6 +433,8 @@ namespace osu.Game.Beatmaps if (existingFileInfo != null) DeleteFile(setInfo, existingFileInfo); + string oldMd5Hash = beatmapInfo.MD5Hash; + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); beatmapInfo.Hash = stream.ComputeSHA2Hash(); @@ -464,6 +451,9 @@ namespace osu.Game.Beatmaps setInfo.CopyChangesToRealm(liveBeatmapSet); + if (transferCollections) + beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); + ProcessBeatmap?.Invoke((liveBeatmapSet, false)); }); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 915f3d4a2a..74ea933255 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -429,10 +429,7 @@ namespace osu.Game.Screens.Edit try { // save the loaded beatmap's data stream. - if (isNewBeatmap) - beatmapManager.SaveNewBeatmap(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); - else - beatmapManager.SaveExistingBeatmap(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); + beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); } catch (Exception ex) { diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 8834911ff5..6e2f1e99cd 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -178,12 +178,7 @@ namespace osu.Game.Tests.Visual => testBeatmapManager.TestBeatmap; } - public override void SaveExistingBeatmap(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) - { - // don't actually care about saving for this context. - } - - public override void SaveNewBeatmap(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { // don't actually care about saving for this context. } From b573e42cc25575588275eec428366fbffb1cc76b Mon Sep 17 00:00:00 2001 From: Jeremiah DECOMBE Date: Tue, 24 Jan 2023 00:08:11 +0100 Subject: [PATCH 035/162] BeatmapSelectionBackgroundBlurLevel renamed to SongSelectBackgroundBlurLevel --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/UserInterfaceStrings.cs | 2 +- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f7b84527b5..d26fc2ec43 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); - SetDefault(OsuSetting.BeatmapSelectionBackgoundBlurLevel, 1f, 0, 1f, 0.01f); + SetDefault(OsuSetting.SongSelectBackgoundBlurLevel, 1f, 0, 1f, 0.01f); // Online settings SetDefault(OsuSetting.Username, string.Empty); @@ -341,7 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, - BeatmapSelectionBackgoundBlurLevel, + SongSelectBackgoundBlurLevel, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 9abb3aaddc..5e4c4dc8ed 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -112,7 +112,7 @@ namespace osu.Game.Localisation /// /// "Song select background blur" /// - public static LocalisableString BeatmapSelectionBackgroundBlurLevel => new TranslatableString(getKey(@"beatmap_selection_background_blur_level"), @"Song select background blur"); + public static LocalisableString SongSelectBackgroundBlurLevel => new TranslatableString(getKey(@"song_select_background_blur_level"), @"Song select background blur"); /// /// "no limit" diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index f0b2721153..3228ebfe44 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -45,8 +45,8 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsSlider { - LabelText = UserInterfaceStrings.BeatmapSelectionBackgroundBlurLevel, - Current = config.GetBindable(OsuSetting.BeatmapSelectionBackgoundBlurLevel) + LabelText = UserInterfaceStrings.SongSelectBackgroundBlurLevel, + Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index c43344d963..b756ac2a98 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - backgroundBlurLevel = config.GetBindable(OsuSetting.BeatmapSelectionBackgoundBlurLevel); + backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel); LoadComponentAsync(Carousel = new BeatmapCarousel { From c1876aac887a238d477892c806314f6b50d33adc Mon Sep 17 00:00:00 2001 From: Jeremiah DECOMBE Date: Tue, 24 Jan 2023 00:36:38 +0100 Subject: [PATCH 036/162] removing parameter name abbreviations --- osu.Game/Screens/Select/SongSelect.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b756ac2a98..eab0620255 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -127,18 +127,18 @@ namespace osu.Game.Screens.Select private Bindable backgroundBlurLevel { get; set; } = new BindableFloat(); - private void applyBackgroundBlur(float v) + private void applyBackgroundBlur(float blurLevel) { ApplyToBackground(background => { background.IgnoreUserSettings.Value = true; - background.BlurAmount.Value = v * BACKGROUND_BLUR; + background.BlurAmount.Value = blurLevel * BACKGROUND_BLUR; }); } - private void applyBackgroundBlur(ValueChangedEvent v) + private void applyBackgroundBlur(ValueChangedEvent blurLevelValueChange) { - applyBackgroundBlur(v.NewValue); + applyBackgroundBlur(blurLevelValueChange.NewValue); } [BackgroundDependencyLoader(true)] From f067a8463150f391437107406fd51cd1ed3db474 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 24 Jan 2023 00:51:07 +0000 Subject: [PATCH 037/162] TestSceneWikiMarkdowContainer: Update image paths --- .../Online/TestSceneWikiMarkdownContainer.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index b353123649..c01ce56d55 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -122,8 +122,8 @@ needs_cleanup: true { AddStep("Add absolute image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh"; - markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; + markdownContainer.Text = "![intro](img/intro-screen.jpg)"; }); } @@ -132,7 +132,7 @@ needs_cleanup: true { AddStep("Add relative image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; markdownContainer.Text = "![intro](img/intro-screen.jpg)"; }); } @@ -142,7 +142,7 @@ needs_cleanup: true { AddStep("Add paragraph with block image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; markdownContainer.Text = @"Line before image ![play menu](img/play-menu.jpg ""Main Menu in osu!"") @@ -156,8 +156,8 @@ Line after image"; { AddStep("Add inline image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh"; - markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/shared/"; + markdownContainer.Text = "![osu! mode icon](mode/osu.png) osu!"; }); } @@ -166,16 +166,16 @@ Line after image"; { AddStep("Add Table", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/shared/judgement/"; markdownContainer.Text = @" | Image | Name | Effect | | :-: | :-: | :-- | -| ![](/wiki/Skinning/Interface/img/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | -| ![](/wiki/Skinning/Interface/img/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | -| ![](/wiki/Skinning/Interface/img/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | -| ![](/wiki/Skinning/Interface/img/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | -| ![](/wiki/Skinning/Interface/img/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | -| ![](/wiki/Skinning/Interface/img/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | +| ![](osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | +| ![](osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | +| ![](osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | +| ![](osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | "; }); } @@ -185,7 +185,7 @@ Line after image"; { AddStep("Add image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Program_files/"; markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; }); From 2454eb8cd9d56f42587e1577ac05824e016ac8f8 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 24 Jan 2023 00:51:40 +0000 Subject: [PATCH 038/162] TestSceneWikiMarkdownContainer: Add visual test for inline and fenced code syntax --- .../Online/TestSceneWikiMarkdownContainer.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index c01ce56d55..7b8d04d25f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -270,6 +270,30 @@ Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed }); } + [Test] + public void TestCodeSyntax() + { + AddStep("set content", () => + { + markdownContainer.Text = @" +This is a paragraph containing `inline code` synatax. +Oh wow I do love the `WikiMarkdownContainer`, it is very cool! + +This is a line before the fenced code block: +```csharp +public class WikiMarkdownContainer : MarkdownContainer +{ + public WikiMarkdownContainer() + { + this.foo = bar; + } +} +``` +This is a line after the fenced code block! +"; + }); + } + private partial class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From 72cfe2ba5a4452ac134060ab90f2dc71ce02cd15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 12:30:11 +0900 Subject: [PATCH 039/162] Move `private` field up with others --- osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index 9842e24d05..2e4f983fc4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.UI /// private readonly List trackedTouches = new List(); + private TrackedTouch? positionTrackingTouch; + private readonly OsuInputManager osuInputManager; private Bindable mouseDisabled = null!; @@ -44,8 +46,6 @@ namespace osu.Game.Rulesets.Osu.UI handleTouchMovement(e); } - private TrackedTouch? positionTrackingTouch; - protected override bool OnTouchDown(TouchDownEvent e) { OsuAction action = trackedTouches.Any(t => t.Action == OsuAction.LeftButton) From 04995b66da5f6abb71ae51eef0628915c1b7df41 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 24 Jan 2023 07:09:25 +0300 Subject: [PATCH 040/162] Add seed slider to Triangles test sceen --- osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs index d3cdf928e8..8a5489f476 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.Background base.LoadComplete(); AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s); + AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s)); } } } From fc558278ccc164d54dcd4d8adf5989e1399f705a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 13:37:12 +0900 Subject: [PATCH 041/162] Fix touch input handler settings not matching search for "touchscreen" --- osu.Game/OsuGameBase.cs | 4 +- .../Settings/Sections/Input/TouchSettings.cs | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a38aa19cef..cf58d07b9e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -556,8 +556,8 @@ namespace osu.Game case JoystickHandler jh: return new JoystickSettings(jh); - case TouchHandler: - return new InputSection.HandlerSection(handler); + case TouchHandler th: + return new TouchSettings(th); } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs new file mode 100644 index 0000000000..8d1b12d5b2 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Handlers.Touch; +using osu.Framework.Localisation; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.Settings.Sections.Input +{ + public partial class TouchSettings : SettingsSubsection + { + private readonly TouchHandler handler; + + public TouchSettings(TouchHandler handler) + { + this.handler = handler; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = CommonStrings.Enabled, + Current = handler.Enabled + }, + }; + } + + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"touchscreen" }); + + protected override LocalisableString Header => handler.Description; + } +} From 04c96355cb682610e14be0b9731b4fba3489f685 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 24 Jan 2023 07:38:42 +0300 Subject: [PATCH 042/162] Use TriangleBorder shader to draw triangles --- osu.Game/Graphics/Backgrounds/Triangles.cs | 40 ++++++++++------------ 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 94397f7ffb..d9dff1574f 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -31,12 +31,6 @@ namespace osu.Game.Graphics.Backgrounds /// private const float equilateral_triangle_ratio = 0.866f; - /// - /// How many screen-space pixels are smoothed over. - /// Same behavior as Sprite's EdgeSmoothness. - /// - private const float edge_smoothness = 1; - private Color4 colourLight = Color4.White; public Color4 ColourLight @@ -115,7 +109,7 @@ namespace osu.Game.Graphics.Backgrounds private void load(IRenderer renderer, ShaderManager shaders) { texture = renderer.WhitePixel; - shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"); } protected override void LoadComplete() @@ -252,14 +246,17 @@ namespace osu.Game.Graphics.Backgrounds private class TrianglesDrawNode : DrawNode { + private float fill = 1f; + protected new Triangles Source => (Triangles)base.Source; private IShader shader; private Texture texture; private readonly List parts = new List(); - private Vector2 size; + private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; + private Vector2 size; private IVertexBatch vertexBatch; public TrianglesDrawNode(Triangles source) @@ -290,29 +287,28 @@ namespace osu.Game.Graphics.Backgrounds } shader.Bind(); - - Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; + shader.GetUniform("thickness").UpdateValue(ref fill); foreach (TriangleParticle particle in parts) { - var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * equilateral_triangle_ratio); + Vector2 relativeSize = Vector2.Divide(triangleSize * particle.Scale, size); - var triangle = new Triangle( - Vector2Extensions.Transform(particle.Position * size, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * size + offset, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) + Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); + Vector2 topRight = topLeft + new Vector2(relativeSize.X, 0f); + Vector2 bottomLeft = topLeft + new Vector2(0f, relativeSize.Y); + Vector2 bottomRight = bottomLeft + new Vector2(relativeSize.X, 0f); + + var drawQuad = new Quad( + Vector2Extensions.Transform(topLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(topRight * size, DrawInfo.Matrix), + Vector2Extensions.Transform(bottomLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(bottomRight * size, DrawInfo.Matrix) ); ColourInfo colourInfo = DrawColourInfo.Colour; colourInfo.ApplyChild(particle.Colour); - renderer.DrawTriangle( - texture, - triangle, - colourInfo, - null, - vertexBatch.AddAction, - Vector2.Divide(localInflationAmount, new Vector2(2 * offset.X, offset.Y))); + renderer.DrawQuad(texture, drawQuad, colourInfo, vertexAction: vertexBatch.AddAction); } shader.Unbind(); From d783998c81f58bd1548e4f6569aca886868845e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Tue, 24 Jan 2023 09:09:05 +0100 Subject: [PATCH 043/162] using BindValueChanged and IsCurrentScreen for setting binding --- osu.Game/Screens/Select/SongSelect.cs | 34 +++++++++------------------ 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index eab0620255..dbd948ff86 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -127,24 +127,21 @@ namespace osu.Game.Screens.Select private Bindable backgroundBlurLevel { get; set; } = new BindableFloat(); - private void applyBackgroundBlur(float blurLevel) - { - ApplyToBackground(background => - { - background.IgnoreUserSettings.Value = true; - background.BlurAmount.Value = blurLevel * BACKGROUND_BLUR; - }); - } - - private void applyBackgroundBlur(ValueChangedEvent blurLevelValueChange) - { - applyBackgroundBlur(blurLevelValueChange.NewValue); - } - [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel); + backgroundBlurLevel.BindValueChanged(e => + { + if (this.IsCurrentScreen()) + { + ApplyToBackground(background => + { + background.IgnoreUserSettings.Value = true; + background.BlurAmount.Value = e.NewValue * BACKGROUND_BLUR; + }); + } + }, true); LoadComponentAsync(Carousel = new BeatmapCarousel { @@ -568,9 +565,6 @@ namespace osu.Game.Screens.Select { base.OnEntering(e); - backgroundBlurLevel.ValueChanged += applyBackgroundBlur; - applyBackgroundBlur(backgroundBlurLevel.Value); - this.FadeInFromZero(250); FilterControl.Activate(); @@ -618,8 +612,6 @@ namespace osu.Game.Screens.Select public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); - backgroundBlurLevel.ValueChanged += applyBackgroundBlur; - applyBackgroundBlur(backgroundBlurLevel.Value); // required due to https://github.com/ppy/osu-framework/issues/3218 ModSelect.SelectedMods.Disabled = false; @@ -665,8 +657,6 @@ namespace osu.Game.Screens.Select // Without this, it's possible for a transfer to happen while we are not the current screen. transferRulesetValue(); - backgroundBlurLevel.ValueChanged -= applyBackgroundBlur; - ModSelect.SelectedMods.UnbindFrom(selectedMods); playExitingTransition(); @@ -675,8 +665,6 @@ namespace osu.Game.Screens.Select public override bool OnExiting(ScreenExitEvent e) { - backgroundBlurLevel.ValueChanged -= applyBackgroundBlur; - if (base.OnExiting(e)) return true; From 7ca2a431e655bea849184e937819b384eb9cdfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Tue, 24 Jan 2023 09:19:53 +0100 Subject: [PATCH 044/162] changing song select background blur setting to boolean --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d26fc2ec43..fff5dc62c6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); - SetDefault(OsuSetting.SongSelectBackgoundBlurLevel, 1f, 0, 1f, 0.01f); + SetDefault(OsuSetting.SongSelectBackgoundBlur, true); // Online settings SetDefault(OsuSetting.Username, string.Empty); @@ -341,7 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, - SongSelectBackgoundBlurLevel, + SongSelectBackgoundBlur, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 3228ebfe44..ab5a3a1280 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -43,10 +43,10 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface Current = config.GetBindable(OsuSetting.ModSelectHotkeyStyle), ClassicDefault = ModSelectHotkeyStyle.Classic }, - new SettingsSlider + new SettingsCheckbox { LabelText = UserInterfaceStrings.SongSelectBackgroundBlurLevel, - Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel) + Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlur) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index dbd948ff86..bbb88052aa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -125,12 +125,12 @@ namespace osu.Game.Screens.Select [Resolved] internal IOverlayManager? OverlayManager { get; private set; } - private Bindable backgroundBlurLevel { get; set; } = new BindableFloat(); + private Bindable backgroundBlurLevel { get; set; } = new BindableBool(); [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel); + backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); backgroundBlurLevel.BindValueChanged(e => { if (this.IsCurrentScreen()) @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Select ApplyToBackground(background => { background.IgnoreUserSettings.Value = true; - background.BlurAmount.Value = e.NewValue * BACKGROUND_BLUR; + background.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0; }); } }, true); @@ -758,7 +758,7 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = backgroundBlurLevel.Value * BACKGROUND_BLUR; + backgroundModeBeatmap.BlurAmount.Value = backgroundBlurLevel.Value ? BACKGROUND_BLUR : 0f; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); From 3a47be6e002eecbea52c443741fad75041261f93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 17:43:14 +0900 Subject: [PATCH 045/162] Fix argon hit circles occasionally going missing during editor seeking --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index db458ec48a..a62efa96bf 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -64,21 +64,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon outerGradient = new Circle // renders the outer bright gradient { Size = new Vector2(OUTER_GRADIENT_SIZE), - Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, innerGradient = new Circle // renders the inner bright gradient { Size = new Vector2(INNER_GRADIENT_SIZE), - Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, innerFill = new Circle // renders the inner dark fill { Size = new Vector2(INNER_FILL_SIZE), - Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -123,14 +120,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // Accent colour may be changed many times during a paused gameplay state. // Schedule the change to avoid transforms piling up. - Scheduler.AddOnce(updateStateTransforms); + Scheduler.AddOnce(() => + { + updateStateTransforms(drawableObject, drawableObject.State.Value); + + ApplyTransformsAt(double.MinValue, true); + ClearTransformsAfter(double.MinValue, true); + }); }, true); drawableObject.ApplyCustomUpdateState += updateStateTransforms; } - private void updateStateTransforms() => updateStateTransforms(drawableObject, drawableObject.State.Value); - private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) From e0a7559d851fca88f92d8866cb54a08f8cd0f6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Tue, 24 Jan 2023 09:55:08 +0100 Subject: [PATCH 046/162] variable naming + loc --- osu.Game/Localisation/UserInterfaceStrings.cs | 4 ++-- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 5e4c4dc8ed..fb9eeeb3de 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -110,9 +110,9 @@ namespace osu.Game.Localisation public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); /// - /// "Song select background blur" + /// "Background blur" /// - public static LocalisableString SongSelectBackgroundBlurLevel => new TranslatableString(getKey(@"song_select_background_blur_level"), @"Song select background blur"); + public static LocalisableString BackgroundBlurLevel => new TranslatableString(getKey(@"background_blur_level"), @"Background blur"); /// /// "no limit" diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index ab5a3a1280..11f7976842 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsCheckbox { - LabelText = UserInterfaceStrings.SongSelectBackgroundBlurLevel, + LabelText = UserInterfaceStrings.BackgroundBlurLevel, Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlur) } }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bbb88052aa..505f932bff 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -125,13 +125,13 @@ namespace osu.Game.Screens.Select [Resolved] internal IOverlayManager? OverlayManager { get; private set; } - private Bindable backgroundBlurLevel { get; set; } = new BindableBool(); + private Bindable configBackgroundBlur { get; set; } = new BindableBool(); [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); - backgroundBlurLevel.BindValueChanged(e => + configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); + configBackgroundBlur.BindValueChanged(e => { if (this.IsCurrentScreen()) { @@ -758,7 +758,7 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = backgroundBlurLevel.Value ? BACKGROUND_BLUR : 0f; + backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); From acb42f7d1265fe8ab7a72d302a4af6d7e12d7bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Tue, 24 Jan 2023 10:18:00 +0100 Subject: [PATCH 047/162] reusing gameplay background blur loc song select background blur --- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 11f7976842..d40d24a528 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsCheckbox { - LabelText = UserInterfaceStrings.BackgroundBlurLevel, + LabelText = GameplaySettingsStrings.BackgroundBlur, Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlur) } }; From c991aa5ca4f1a947c92e637228ee28c2b865e87b Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 24 Jan 2023 22:01:53 +0000 Subject: [PATCH 048/162] TestSceneWikiMarkdownContainer: Revert CurrentPath changes and update markdown img links only --- .../Online/TestSceneWikiMarkdownContainer.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 7b8d04d25f..ab9fae5d2c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -122,8 +122,8 @@ needs_cleanup: true { AddStep("Add absolute image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; - markdownContainer.Text = "![intro](img/intro-screen.jpg)"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; + markdownContainer.Text = "![intro](wiki/images/Client/Interface/img/intro-screen.jpg)"; }); } @@ -132,8 +132,8 @@ needs_cleanup: true { AddStep("Add relative image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; - markdownContainer.Text = "![intro](img/intro-screen.jpg)"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; + markdownContainer.Text = "![intro](../images/Client/Interface/img/intro-screen.jpg)"; }); } @@ -142,10 +142,10 @@ needs_cleanup: true { AddStep("Add paragraph with block image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; markdownContainer.Text = @"Line before image -![play menu](img/play-menu.jpg ""Main Menu in osu!"") +![play menu](../images/Client/Interface/img/play-menu.jpg ""Main Menu in osu!"") Line after image"; }); @@ -156,8 +156,8 @@ Line after image"; { AddStep("Add inline image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/shared/"; - markdownContainer.Text = "![osu! mode icon](mode/osu.png) osu!"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; + markdownContainer.Text = "![osu! mode icon](wiki/shared/mode/osu.png) osu!"; }); } @@ -166,16 +166,16 @@ Line after image"; { AddStep("Add Table", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/shared/judgement/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; markdownContainer.Text = @" | Image | Name | Effect | | :-: | :-: | :-- | -| ![](osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | -| ![](osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | -| ![](osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | -| ![](osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | -| ![](osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | -| ![](osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](wiki/images/shared/judgement/osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | +| ![](wiki/images/shared/judgement/osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | +| ![](wiki/images/shared/judgement/osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | +| ![](wiki/images/shared/judgement/osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | +| ![](wiki/images/shared/judgement/osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](wiki/images/shared/judgement/osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | "; }); } @@ -185,8 +185,8 @@ Line after image"; { AddStep("Add image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Program_files/"; - markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/"; + markdownContainer.Text = "![](../images/Client/Program_files/img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; }); AddUntilStep("Wait image to load", () => markdownContainer.ChildrenOfType().First().DelayedLoadCompleted); From ff22a91d5287aaf19b7a8f5f80dfba2e7b75b92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Dec 2022 19:36:17 +0100 Subject: [PATCH 049/162] Move user cover lower down --- .../Profile/Header/TopHeaderContainer.cs | 165 +++++++++++------- osu.Game/Overlays/Profile/ProfileHeader.cs | 35 +--- 2 files changed, 99 insertions(+), 101 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 1fe39cb570..270be7cdf4 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; @@ -28,6 +29,7 @@ namespace osu.Game.Overlays.Profile.Header [Resolved] private IAPIProvider api { get; set; } = null!; + private UserCoverBackground cover = null!; private SupporterIcon supporterTag = null!; private UpdateableAvatar avatar = null!; private OsuSpriteText usernameText = null!; @@ -40,7 +42,8 @@ namespace osu.Game.Overlays.Profile.Header [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - Height = 150; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; InternalChildren = new Drawable[] { @@ -51,52 +54,73 @@ namespace osu.Game.Overlays.Profile.Header }, new FillFlowContainer { - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, - Height = avatar_size, - AutoSizeAxes = Axes.X, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, Children = new Drawable[] { - avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) + cover = new ProfileCoverBackground { - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = avatar_size * 0.25f, + RelativeSizeAxes = Axes.X, + Height = 250, }, - new OsuContextMenuContainer + new FillFlowContainer { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Child = new Container + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Padding = new MarginPadding { Left = 10 }, - Children = new Drawable[] + Left = UserProfileOverlay.CONTENT_X_MARGIN, + Vertical = 10 + }, + Height = avatar_size, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) { - new FillFlowContainer + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + }, + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Child = new Container { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Padding = new MarginPadding { Left = 10 }, Children = new Drawable[] { new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), + Direction = FillDirection.Vertical, Children = new Drawable[] { - usernameText = new OsuSpriteText + new FillFlowContainer { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + }, + openUserExternally = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } }, - openUserExternally = new ExternalLinkButton + titleText = new OsuSpriteText { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) }, groupBadgeFlow = new GroupBadgeFlow { @@ -105,54 +129,50 @@ namespace osu.Game.Overlays.Profile.Header } } }, - titleText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) - }, - } - }, - new FillFlowContainer - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - supporterTag = new SupporterIcon - { - Height = 20, - Margin = new MarginPadding { Top = 5 } - }, - new Box - { - RelativeSizeAxes = Axes.X, - Height = 1.5f, - Margin = new MarginPadding { Top = 10 }, - Colour = colourProvider.Light1, - }, new FillFlowContainer { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Direction = FillDirection.Horizontal, Children = new Drawable[] { - userFlag = new UpdateableFlag + supporterTag = new SupporterIcon { - Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, + Height = 20, + Margin = new MarginPadding { Top = 5 } }, - userCountryText = new OsuSpriteText + new Box { - Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), - Margin = new MarginPadding { Left = 10 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Margin = new MarginPadding { Top = 10 }, Colour = colourProvider.Light1, - } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + userFlag = new UpdateableFlag + { + Size = new Vector2(28, 20), + ShowPlaceholderOnUnknown = false, + }, + userCountryText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 10 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Colour = colourProvider.Light1, + } + } + }, } - }, + } } } } @@ -169,6 +189,7 @@ namespace osu.Game.Overlays.Profile.Header { var user = data?.User; + cover.User = user; avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}"; @@ -179,5 +200,15 @@ namespace osu.Game.Overlays.Profile.Header titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); groupBadgeFlow.User.Value = user; } + + private partial class ProfileCoverBackground : UserCoverBackground + { + protected override double LoadDelay => 0; + + public ProfileCoverBackground() + { + Masking = true; + } + } } } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c559a1c102..67b723b7e6 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -3,23 +3,17 @@ using System.Diagnostics; using osu.Framework.Bindables; -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.Framework.Localisation; using osu.Game.Overlays.Profile.Header; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Resources.Localisation.Web; -using osu.Game.Users; namespace osu.Game.Overlays.Profile { public partial class ProfileHeader : TabControlOverlayHeader { - private UserCoverBackground coverContainer = null!; - public Bindable User = new Bindable(); private CentreHeaderContainer centreHeaderContainer; @@ -29,8 +23,6 @@ namespace osu.Game.Overlays.Profile { ContentSidePadding = UserProfileOverlay.CONTENT_X_MARGIN; - User.ValueChanged += e => updateDisplay(e.NewValue); - TabControl.AddItem(LayoutStrings.HeaderUsersShow); // todo: pending implementation. @@ -41,25 +33,7 @@ namespace osu.Game.Overlays.Profile Debug.Assert(detailHeaderContainer != null); } - protected override Drawable CreateBackground() => - new Container - { - RelativeSizeAxes = Axes.X, - Height = 150, - Masking = true, - Children = new Drawable[] - { - coverContainer = new ProfileCoverBackground - { - RelativeSizeAxes = Axes.Both, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("222").Opacity(0.8f), Color4Extensions.FromHex("222").Opacity(0.2f)) - }, - } - }; + protected override Drawable CreateBackground() => Empty(); protected override Drawable CreateContent() => new FillFlowContainer { @@ -103,8 +77,6 @@ namespace osu.Game.Overlays.Profile User = { BindTarget = User } }; - private void updateDisplay(UserProfileData? user) => coverContainer.User = user?.User; - private partial class ProfileHeaderTitle : OverlayTitle { public ProfileHeaderTitle() @@ -113,10 +85,5 @@ namespace osu.Game.Overlays.Profile IconTexture = "Icons/Hexacons/profile"; } } - - private partial class ProfileCoverBackground : UserCoverBackground - { - protected override double LoadDelay => 0; - } } } From ef7812412b5eadf301de7aa2d7e090f450d76d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Dec 2022 19:58:58 +0100 Subject: [PATCH 050/162] Update top header container appearance --- .../Online/TestSceneUserProfileOverlay.cs | 5 +- .../Profile/Header/TopHeaderContainer.cs | 114 ++++++++---------- 2 files changed, 54 insertions(+), 65 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index fc8c2c0b6e..9aaa616c04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - CountryCode = CountryCode.Unknown, + CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, @@ -143,7 +143,8 @@ namespace osu.Game.Tests.Visual.Online { Available = 10, Total = 50 - } + }, + SupportLevel = 2, }; } } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 270be7cdf4..a71db23989 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions; 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.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Profile.Header { public partial class TopHeaderContainer : CompositeDrawable { - private const float avatar_size = 110; + private const float avatar_size = 120; public readonly Bindable User = new Bindable(); @@ -67,60 +68,66 @@ namespace osu.Game.Overlays.Profile.Header new FillFlowContainer { Direction = FillDirection.Horizontal, - Margin = new MarginPadding + Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - Height = avatar_size, + Spacing = new Vector2(20, 0), + Height = 85, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, Children = new Drawable[] { avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, Size = new Vector2(avatar_size), Masking = true, CornerRadius = avatar_size * 0.25f, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.25f), + } }, new OsuContextMenuContainer { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Child = new Container + Child = new FillFlowContainer { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Padding = new MarginPadding { Left = 10 }, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Children = new Drawable[] { new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), Children = new Drawable[] { - new FillFlowContainer + usernameText = new OsuSpriteText { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - usernameText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) - }, - openUserExternally = new ExternalLinkButton - { - Margin = new MarginPadding { Left = 5 }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) }, - titleText = new OsuSpriteText + supporterTag = new SupporterIcon { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 15, + }, + openUserExternally = new ExternalLinkButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, groupBadgeFlow = new GroupBadgeFlow { @@ -129,52 +136,33 @@ namespace osu.Game.Overlays.Profile.Header } } }, + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + Margin = new MarginPadding { Bottom = 5 } + }, new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, Children = new Drawable[] { - supporterTag = new SupporterIcon + userFlag = new UpdateableFlag { - Height = 20, - Margin = new MarginPadding { Top = 5 } + Size = new Vector2(28, 20), + ShowPlaceholderOnUnknown = false, }, - new Box + userCountryText = new OsuSpriteText { - RelativeSizeAxes = Axes.X, - Height = 1.5f, - Margin = new MarginPadding { Top = 10 }, - Colour = colourProvider.Light1, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - userFlag = new UpdateableFlag - { - Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, - }, - userCountryText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), - Margin = new MarginPadding { Left = 10 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Colour = colourProvider.Light1, - } - } - }, + Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 5 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + } } - } + }, } - } + }, } } } From e74176e5bd49fdf2462bf93fbf81225c5b7bb5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Dec 2022 20:09:49 +0100 Subject: [PATCH 051/162] Add cover toggle button --- ...dDetailsButton.cs => ToggleCoverButton.cs} | 21 +- .../Profile/Header/TopHeaderContainer.cs | 198 ++++++++++-------- 2 files changed, 117 insertions(+), 102 deletions(-) rename osu.Game/Overlays/Profile/Header/Components/{ExpandDetailsButton.cs => ToggleCoverButton.cs} (68%) diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs similarity index 68% rename from osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs rename to osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs index e7a83c95d8..9ae529f3ae 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs @@ -5,7 +5,6 @@ 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; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -15,11 +14,11 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class ExpandDetailsButton : ProfileHeaderButton + public partial class ToggleCoverButton : ProfileHeaderButton { - public readonly BindableBool DetailsVisible = new BindableBool(); + public readonly BindableBool CoverVisible = new BindableBool(); - public override LocalisableString TooltipText => DetailsVisible.Value ? CommonStrings.ButtonsCollapse : CommonStrings.ButtonsExpand; + public override LocalisableString TooltipText => CoverVisible.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; private SpriteIcon icon = null!; private Sample? sampleOpen; @@ -27,12 +26,12 @@ namespace osu.Game.Overlays.Profile.Header.Components protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(); - public ExpandDetailsButton() + public ToggleCoverButton() { Action = () => { - DetailsVisible.Toggle(); - (DetailsVisible.Value ? sampleOpen : sampleClose)?.Play(); + CoverVisible.Toggle(); + (CoverVisible.Value ? sampleOpen : sampleClose)?.Play(); }; } @@ -40,19 +39,21 @@ namespace osu.Game.Overlays.Profile.Header.Components private void load(OverlayColourProvider colourProvider, AudioManager audio) { IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background2.Lighten(0.2f); + HoverColour = colourProvider.Background1; sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); sampleClose = audio.Samples.Get(@"UI/dropdown-close"); + AutoSizeAxes = Axes.None; + Size = new Vector2(30); Child = icon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(20, 12) + Size = new Vector2(10.5f, 12) }; - DetailsVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + CoverVisible.BindValueChanged(visible => updateState(visible.NewValue), true); } private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index a71db23989..0204800326 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -65,108 +65,122 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.X, Height = 250, }, - new FillFlowContainer + new Container { - Direction = FillDirection.Horizontal, - Padding = new MarginPadding - { - Left = UserProfileOverlay.CONTENT_X_MARGIN, - Vertical = 10 - }, - Spacing = new Vector2(20, 0), - Height = 85, RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Children = new Drawable[] { - avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) + new FillFlowContainer { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = avatar_size * 0.25f, - EdgeEffect = new EdgeEffectParameters + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0, 1), - Radius = 3, - Colour = Colour4.Black.Opacity(0.25f), + Left = UserProfileOverlay.CONTENT_X_MARGIN, + Vertical = 10 + }, + Spacing = new Vector2(20, 0), + Height = 85, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.25f), + } + }, + new OsuContextMenuContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + }, + supporterTag = new SupporterIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 15, + }, + openUserExternally = new ExternalLinkButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + groupBadgeFlow = new GroupBadgeFlow + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + Margin = new MarginPadding { Bottom = 5 } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + userFlag = new UpdateableFlag + { + Size = new Vector2(28, 20), + ShowPlaceholderOnUnknown = false, + }, + userCountryText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 5 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + } + } + }, + } + }, + }, } }, - new OsuContextMenuContainer + new ToggleCoverButton { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - usernameText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) - }, - supporterTag = new SupporterIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Height = 15, - }, - openUserExternally = new ExternalLinkButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - groupBadgeFlow = new GroupBadgeFlow - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - } - } - }, - titleText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), - Margin = new MarginPadding { Bottom = 5 } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - userFlag = new UpdateableFlag - { - Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, - }, - userCountryText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular), - Margin = new MarginPadding { Left = 5 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - } - } - }, - } - }, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 } } - } - } - } + }, + }, + }, }, }; From 33e91cf51227294bec0ccf74904ef18813aafd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Dec 2022 20:28:28 +0100 Subject: [PATCH 052/162] Implement cover toggling --- .../Header/Components/ToggleCoverButton.cs | 2 +- .../Profile/Header/TopHeaderContainer.cs | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs index 9ae529f3ae..ef13f089c3 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class ToggleCoverButton : ProfileHeaderButton { - public readonly BindableBool CoverVisible = new BindableBool(); + public readonly BindableBool CoverVisible = new BindableBool(true); public override LocalisableString TooltipText => CoverVisible.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 0204800326..ce0c56b52b 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -23,7 +23,8 @@ namespace osu.Game.Overlays.Profile.Header { public partial class TopHeaderContainer : CompositeDrawable { - private const float avatar_size = 120; + private const float content_height = 65; + private const float vertical_padding = 10; public readonly Bindable User = new Bindable(); @@ -39,6 +40,7 @@ namespace osu.Game.Overlays.Profile.Header private UpdateableFlag userFlag = null!; private OsuSpriteText userCountryText = null!; private GroupBadgeFlow groupBadgeFlow = null!; + private ToggleCoverButton coverToggle = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -63,7 +65,6 @@ namespace osu.Game.Overlays.Profile.Header cover = new ProfileCoverBackground { RelativeSizeAxes = Axes.X, - Height = 250, }, new Container { @@ -77,10 +78,10 @@ namespace osu.Game.Overlays.Profile.Header Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, - Vertical = 10 + Vertical = vertical_padding }, Spacing = new Vector2(20, 0), - Height = 85, + Height = content_height + 2 * vertical_padding, RelativeSizeAxes = Axes.X, Children = new Drawable[] { @@ -88,9 +89,7 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Size = new Vector2(avatar_size), Masking = true, - CornerRadius = avatar_size * 0.25f, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -172,7 +171,7 @@ namespace osu.Game.Overlays.Profile.Header }, } }, - new ToggleCoverButton + coverToggle = new ToggleCoverButton { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -183,8 +182,15 @@ namespace osu.Game.Overlays.Profile.Header }, }, }; + } - User.BindValueChanged(user => updateUser(user.NewValue)); + protected override void LoadComplete() + { + base.LoadComplete(); + + User.BindValueChanged(user => updateUser(user.NewValue), true); + coverToggle.CoverVisible.BindValueChanged(_ => updateCoverState(), true); + FinishTransforms(true); } private void updateUser(UserProfileData? data) @@ -203,6 +209,15 @@ namespace osu.Game.Overlays.Profile.Header groupBadgeFlow.User.Value = user; } + private void updateCoverState() + { + const float transition_duration = 250; + + cover.ResizeHeightTo(coverToggle.CoverVisible.Value ? 250 : 0, transition_duration, Easing.OutQuint); + avatar.ResizeTo(new Vector2(coverToggle.CoverVisible.Value ? 120 : content_height), transition_duration, Easing.OutQuint); + avatar.TransformTo(nameof(avatar.CornerRadius), coverToggle.CoverVisible.Value ? 40f : 20f, transition_duration, Easing.OutQuint); + } + private partial class ProfileCoverBackground : UserCoverBackground { protected override double LoadDelay => 0; From f2df36e6a556be58a73df652d778aea35b0e780b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Jan 2023 23:30:07 +0100 Subject: [PATCH 053/162] Persist cover visibility to user settings Follows web precedent, wherein the setting is saved to local storage. --- .../Online/TestSceneUserProfileHeader.cs | 20 +++++++++++++++++++ osu.Game/Configuration/OsuConfigManager.cs | 3 +++ .../Header/Components/ToggleCoverButton.cs | 10 +++++----- .../Profile/Header/TopHeaderContainer.cs | 18 +++++++++++------ 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 513c473830..640e895b6c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Profile; @@ -19,6 +20,9 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + private ProfileHeader header = null!; [SetUpSteps] @@ -33,6 +37,22 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); } + [Test] + public void TestProfileCoverExpanded() + { + AddStep("Set cover to expanded", () => configManager.SetValue(OsuSetting.ProfileCoverExpanded, true)); + AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); + AddUntilStep("Cover is expanded", () => header.ChildrenOfType().Single().Height, () => Is.GreaterThan(0)); + } + + [Test] + public void TestProfileCoverCollapsed() + { + AddStep("Set cover to collapsed", () => configManager.SetValue(OsuSetting.ProfileCoverExpanded, false)); + AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); + AddUntilStep("Cover is collapsed", () => header.ChildrenOfType().Single().Height, () => Is.EqualTo(0)); + } + [Test] public void TestOnlineState() { diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6cbb677a64..507d5e611c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -58,6 +58,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal); + SetDefault(OsuSetting.ProfileCoverExpanded, true); + SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); // Online settings @@ -375,5 +377,6 @@ namespace osu.Game.Configuration LastProcessedMetadataId, SafeAreaConsiderations, ComboColourNormalisationAmount, + ProfileCoverExpanded, } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs index ef13f089c3..9171d5de7d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs @@ -16,9 +16,9 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class ToggleCoverButton : ProfileHeaderButton { - public readonly BindableBool CoverVisible = new BindableBool(true); + public readonly BindableBool CoverExpanded = new BindableBool(true); - public override LocalisableString TooltipText => CoverVisible.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; + public override LocalisableString TooltipText => CoverExpanded.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; private SpriteIcon icon = null!; private Sample? sampleOpen; @@ -30,8 +30,8 @@ namespace osu.Game.Overlays.Profile.Header.Components { Action = () => { - CoverVisible.Toggle(); - (CoverVisible.Value ? sampleOpen : sampleClose)?.Play(); + CoverExpanded.Toggle(); + (CoverExpanded.Value ? sampleOpen : sampleClose)?.Play(); }; } @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Header.Components Size = new Vector2(10.5f, 12) }; - CoverVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + CoverExpanded.BindValueChanged(visible => updateState(visible.NewValue), true); } private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index ce0c56b52b..389f2301c4 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Cursor; @@ -42,12 +43,16 @@ namespace osu.Game.Overlays.Profile.Header private GroupBadgeFlow groupBadgeFlow = null!; private ToggleCoverButton coverToggle = null!; + private Bindable coverExpanded = null!; + [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuConfigManager configManager) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + coverExpanded = configManager.GetBindable(OsuSetting.ProfileCoverExpanded); + InternalChildren = new Drawable[] { new Box @@ -175,7 +180,8 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 10 } + Margin = new MarginPadding { Right = 10 }, + CoverExpanded = { BindTarget = coverExpanded } } }, }, @@ -189,7 +195,7 @@ namespace osu.Game.Overlays.Profile.Header base.LoadComplete(); User.BindValueChanged(user => updateUser(user.NewValue), true); - coverToggle.CoverVisible.BindValueChanged(_ => updateCoverState(), true); + coverExpanded.BindValueChanged(_ => updateCoverState(), true); FinishTransforms(true); } @@ -213,9 +219,9 @@ namespace osu.Game.Overlays.Profile.Header { const float transition_duration = 250; - cover.ResizeHeightTo(coverToggle.CoverVisible.Value ? 250 : 0, transition_duration, Easing.OutQuint); - avatar.ResizeTo(new Vector2(coverToggle.CoverVisible.Value ? 120 : content_height), transition_duration, Easing.OutQuint); - avatar.TransformTo(nameof(avatar.CornerRadius), coverToggle.CoverVisible.Value ? 40f : 20f, transition_duration, Easing.OutQuint); + cover.ResizeHeightTo(coverToggle.CoverExpanded.Value ? 250 : 0, transition_duration, Easing.OutQuint); + avatar.ResizeTo(new Vector2(coverToggle.CoverExpanded.Value ? 120 : content_height), transition_duration, Easing.OutQuint); + avatar.TransformTo(nameof(avatar.CornerRadius), coverToggle.CoverExpanded.Value ? 40f : 20f, transition_duration, Easing.OutQuint); } private partial class ProfileCoverBackground : UserCoverBackground From 4d7cd59df39faa42f462f20e39ea4db45ebd5af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Jan 2023 23:58:39 +0100 Subject: [PATCH 054/162] Fix `TestAbsoluteImage()` no longer actually testing absolute image URL --- osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index ab9fae5d2c..ba9830181e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -123,7 +123,7 @@ needs_cleanup: true AddStep("Add absolute image", () => { markdownContainer.CurrentPath = "https://dev.ppy.sh"; - markdownContainer.Text = "![intro](wiki/images/Client/Interface/img/intro-screen.jpg)"; + markdownContainer.Text = "![intro](/wiki/images/Client/Interface/img/intro-screen.jpg)"; }); } From a3bb6d1fab3a658d1b3566fa602db763c741814f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Jan 2023 00:00:16 +0100 Subject: [PATCH 055/162] Revert a few more relative to absolute changes Because not sure why they were changed in the first place. --- .../Online/TestSceneWikiMarkdownContainer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index ba9830181e..0aa0295f7d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -157,7 +157,7 @@ Line after image"; AddStep("Add inline image", () => { markdownContainer.CurrentPath = "https://dev.ppy.sh"; - markdownContainer.Text = "![osu! mode icon](wiki/shared/mode/osu.png) osu!"; + markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; }); } @@ -170,12 +170,12 @@ Line after image"; markdownContainer.Text = @" | Image | Name | Effect | | :-: | :-: | :-- | -| ![](wiki/images/shared/judgement/osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | -| ![](wiki/images/shared/judgement/osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | -| ![](wiki/images/shared/judgement/osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | -| ![](wiki/images/shared/judgement/osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | -| ![](wiki/images/shared/judgement/osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | -| ![](wiki/images/shared/judgement/osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](/wiki/images/shared/judgement/osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | +| ![](/wiki/images/shared/judgement/osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | +| ![](/wiki/images/shared/judgement/osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | +| ![](/wiki/images/shared/judgement/osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | +| ![](/wiki/images/shared/judgement/osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](/wiki/images/shared/judgement/osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | "; }); } From 4d4dfd9e8b7b263734edec6cb692e9174df8d7bc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Jan 2023 02:15:01 +0300 Subject: [PATCH 056/162] Fix iOS workflow still targeting old Xcode version --- .github/workflows/ci.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 213c5082ab..5c11f91994 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,21 +121,12 @@ jobs: build-only-ios: name: Build only (iOS) - # change to macos-latest once GitHub finishes migrating all repositories to macOS 12. - runs-on: macos-12 + runs-on: macos-latest timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v2 - # see https://github.com/actions/runner-images/issues/6771#issuecomment-1354713617 - # remove once all workflow VMs use Xcode 14.1 - - name: Set Xcode Version - shell: bash - run: | - sudo xcode-select -s "/Applications/Xcode_14.1.app" - echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.1.app" >> $GITHUB_ENV - - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: From d8365f4fca4bfc0c52f261eaf79e67eafa7a8eb5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 11:47:15 +0900 Subject: [PATCH 057/162] Reverse order of application to match `DrawableHitObject` --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index a62efa96bf..23c18d9207 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -122,10 +122,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // Schedule the change to avoid transforms piling up. Scheduler.AddOnce(() => { - updateStateTransforms(drawableObject, drawableObject.State.Value); - ApplyTransformsAt(double.MinValue, true); ClearTransformsAfter(double.MinValue, true); + + updateStateTransforms(drawableObject, drawableObject.State.Value); }); }, true); From 5f037ac1e95493f87847a34ff4f08d260abb95da Mon Sep 17 00:00:00 2001 From: itsMapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Tue, 24 Jan 2023 22:24:21 -0600 Subject: [PATCH 058/162] comment not useful --- .../Skinning/Argon/ArgonHoldNoteTailPiece.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs index 02325904c4..a14abf7011 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs @@ -23,8 +23,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon public ArgonHoldNoteTailPiece() { RelativeSizeAxes = Axes.X; - - // multiply by two so that the hold body extends up to the height of the note head accent Height = ArgonNotePiece.NOTE_HEIGHT * 2; CornerRadius = ArgonNotePiece.CORNER_RADIUS; From 132417b4e4e9880e3336783743c120afe7e18a0c Mon Sep 17 00:00:00 2001 From: itsMapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Tue, 24 Jan 2023 22:36:13 -0600 Subject: [PATCH 059/162] fix braces --- .../Skinning/Argon/ArgonHoldNoteTailPiece.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs index a14abf7011..b2cccfac26 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs @@ -30,12 +30,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon InternalChildren = new Drawable[] { - shadeContainer = new Container { + shadeContainer = new Container + { RelativeSizeAxes = Axes.Both, Height = 0.5f, CornerRadius = ArgonNotePiece.CORNER_RADIUS, Masking = true, - Children = new Drawable[] { + Children = new Drawable[] + { new Box { RelativeSizeAxes = Axes.Both, @@ -44,7 +46,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon }, }, }, - hitLine = new Circle { + hitLine = new Circle + { RelativeSizeAxes = Axes.X, Height = ArgonNotePiece.CORNER_RADIUS * 2, }, From 739ec8d81d7c680aca106e7b49a4d7d43de67ca2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:04:30 +0900 Subject: [PATCH 060/162] Add argument hint for nondescript `bool` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index fc6d4081e7..89fe498f89 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, false); + save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, transferCollections: false); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); From 741ca968531f33dbd2f099a3d32d00d23ac4cb39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:09:13 +0900 Subject: [PATCH 061/162] Make `transferCollections` argument to private method explicitly required --- osu.Game/Beatmaps/BeatmapManager.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 89fe498f89..f39523cedf 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -286,10 +286,8 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) - { - save(beatmapInfo, beatmapContent, beatmapSkin); - } + public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) => + save(beatmapInfo, beatmapContent, beatmapSkin, transferCollections: true); public void DeleteAllVideos() { @@ -399,7 +397,7 @@ namespace osu.Game.Beatmaps setInfo.Status = BeatmapOnlineStatus.LocallyModified; } - private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin, bool transferCollections = true) + private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin, bool transferCollections) { var setInfo = beatmapInfo.BeatmapSet; Debug.Assert(setInfo != null); From 392ff2ffea6273063402d97580df9123b4874dd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:11:02 +0900 Subject: [PATCH 062/162] Reword comment regarding hash transfer to make more sense --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f39523cedf..ad56bbbc3a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -280,9 +280,11 @@ namespace osu.Game.Beatmaps public IWorkingBeatmap DefaultBeatmap => workingBeatmapCache.DefaultBeatmap; /// - /// Saves an existing file against a given , also transferring the beatmap - /// hashes in any collections referencing it. + /// Saves an existing file against a given . /// + /// + /// This method will also update any user beatmap collection hash references to the new post-saved hash. + /// /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. From 3e91dd2a16094a84f6fdbeeadfbafe06a14999db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:27:44 +0900 Subject: [PATCH 063/162] Update spacing along with expanded state --- .../Overlays/Profile/Header/TopHeaderContainer.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 389f2301c4..57197ab0de 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -45,6 +45,8 @@ namespace osu.Game.Overlays.Profile.Header private Bindable coverExpanded = null!; + private FillFlowContainer flow = null!; + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuConfigManager configManager) { @@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Profile.Header AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new FillFlowContainer + flow = new FillFlowContainer { Direction = FillDirection.Horizontal, Padding = new MarginPadding @@ -85,7 +87,6 @@ namespace osu.Game.Overlays.Profile.Header Left = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = vertical_padding }, - Spacing = new Vector2(20, 0), Height = content_height + 2 * vertical_padding, RelativeSizeAxes = Axes.X, Children = new Drawable[] @@ -219,9 +220,12 @@ namespace osu.Game.Overlays.Profile.Header { const float transition_duration = 250; - cover.ResizeHeightTo(coverToggle.CoverExpanded.Value ? 250 : 0, transition_duration, Easing.OutQuint); - avatar.ResizeTo(new Vector2(coverToggle.CoverExpanded.Value ? 120 : content_height), transition_duration, Easing.OutQuint); - avatar.TransformTo(nameof(avatar.CornerRadius), coverToggle.CoverExpanded.Value ? 40f : 20f, transition_duration, Easing.OutQuint); + bool expanded = coverToggle.CoverExpanded.Value; + + cover.ResizeHeightTo(expanded ? 250 : 0, transition_duration, Easing.OutQuint); + avatar.ResizeTo(new Vector2(expanded ? 120 : content_height), transition_duration, Easing.OutQuint); + avatar.TransformTo(nameof(avatar.CornerRadius), expanded ? 40f : 20f, transition_duration, Easing.OutQuint); + flow.TransformTo(nameof(flow.Spacing), new Vector2(expanded ? 20f : 10f), transition_duration, Easing.OutQuint); } private partial class ProfileCoverBackground : UserCoverBackground From 6bf7773532c117791c67451bae8280b84f2748cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:27:54 +0900 Subject: [PATCH 064/162] Increase duration of expansion transition --- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 57197ab0de..652ebcec26 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -218,7 +218,7 @@ namespace osu.Game.Overlays.Profile.Header private void updateCoverState() { - const float transition_duration = 250; + const float transition_duration = 500; bool expanded = coverToggle.CoverExpanded.Value; From 598c6fcbad58333eee5f0ab6e515989ba53ae308 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 15:48:57 +0900 Subject: [PATCH 065/162] Add basic support for stupidly long hold note skin textures --- .../special-skin/LongNoteTailWang.png | Bin 0 -> 101181 bytes .../Resources/special-skin/skin.ini | 4 +- .../Skinning/Legacy/LegacyBodyPiece.cs | 6 +- osu.Game/Skinning/Skin.cs | 56 +++++++++++++++++- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png new file mode 100644 index 0000000000000000000000000000000000000000..982cc1d259efcd670f0e7baf338a11059c7b098c GIT binary patch literal 101181 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
  • m@nicEnXm?)|Fu4vV*{ZH((wm)g|Fp## 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini index 9c987efc60..7c51036d69 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -14,4 +14,6 @@ Hit200: mania/hit200@2x Hit300: mania/hit300@2x Hit300g: mania/hit300g@2x StageLeft: mania/stage-left -StageRight: mania/stage-right \ No newline at end of file +StageRight: mania/stage-right +NoteImage0L: LongNoteTailWang +NoteImage1L: LongNoteTailWang diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index c928ebb3e0..6887674a1f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -54,6 +54,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy float lightScale = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value ?? 1; + float minimumColumnWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.MinimumColumnWidth)?.Value + ?? 1; + // Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length. // This animation is discarded and re-queried with the appropriate frame length afterwards. var tmp = skin.GetAnimation(lightImage, true, false); @@ -92,7 +95,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy d.RelativeSizeAxes = Axes.Both; d.Size = Vector2.One; d.FillMode = FillMode.Stretch; - // Todo: Wrap + d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; + // Todo: Wrap? }); if (bodySprite != null) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 19e8bc7092..2761a93290 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -7,9 +7,12 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -18,6 +21,8 @@ using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; using osu.Game.Screens.Play.HUD; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; namespace osu.Game.Skinning { @@ -77,7 +82,7 @@ namespace osu.Game.Skinning (storage as ResourceStore)?.AddExtension("ogg"); Samples = samples; - Textures = new TextureStore(resources.Renderer, resources.CreateTextureLoaderStore(storage)); + Textures = new TextureStore(resources.Renderer, new SquishingTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); } else { @@ -210,5 +215,54 @@ namespace osu.Game.Skinning } #endregion + + public class SquishingTextureLoaderStore : IResourceStore + { + private readonly IResourceStore textureStore; + + public SquishingTextureLoaderStore(IResourceStore textureStore) + { + this.textureStore = textureStore; + } + + public void Dispose() + { + textureStore.Dispose(); + } + + public TextureUpload Get(string name) + { + var textureUpload = textureStore.Get(name); + + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureUpload.IsNull()) + return null!; + + // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. + // To the point where GPUs cannot load the textures (along with most image editor apps). + // To work around this, let's look out for any stupid images and shrink them down into a usable size. + const int max_supported_texture_size = 16384; + + if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) + { + var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + + image.Mutate(i => i.Resize(new Size( + Math.Min(textureUpload.Width, max_supported_texture_size), + Math.Min(textureUpload.Height, max_supported_texture_size) + ))); + + return new TextureUpload(image); + } + + return textureUpload; + } + + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => textureStore.GetAsync(name, cancellationToken); + + public Stream GetStream(string name) => textureStore.GetStream(name); + + public IEnumerable GetAvailableResources() => textureStore.GetAvailableResources(); + } } } From d000a4ed28967ef7a40c541acb26df6969c82373 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 15:56:10 +0900 Subject: [PATCH 066/162] Make sure to dispose of the original texture upload as we are replacing it --- osu.Game/Skinning/Skin.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 2761a93290..298a94920f 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -247,6 +247,9 @@ namespace osu.Game.Skinning { var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + // The original texture upload will no longer be returned or used. + textureUpload.Dispose(); + image.Mutate(i => i.Resize(new Size( Math.Min(textureUpload.Width, max_supported_texture_size), Math.Min(textureUpload.Height, max_supported_texture_size) From c1a5c16973d2a0a62b7393e5d647e2035793fa0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 16:39:03 +0900 Subject: [PATCH 067/162] Reduce maximum texture size to a more commonly-supported `8192` --- osu.Game/Skinning/Skin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 298a94920f..c862a51870 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -241,7 +241,7 @@ namespace osu.Game.Skinning // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. // To the point where GPUs cannot load the textures (along with most image editor apps). // To work around this, let's look out for any stupid images and shrink them down into a usable size. - const int max_supported_texture_size = 16384; + const int max_supported_texture_size = 8192; if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) { From 0a9b20d5d5f3a734e8b8f25dcefe8d6d08a41c22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 20:01:00 +0900 Subject: [PATCH 068/162] Split lookup store into own file / class --- .../MaxDimensionLimitedTextureLoaderStore.cs | 69 ++++++++++++++++ osu.Game/Skinning/Skin.cs | 80 +++---------------- 2 files changed, 82 insertions(+), 67 deletions(-) create mode 100644 osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs diff --git a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs new file mode 100644 index 0000000000..94d7afaf7e --- /dev/null +++ b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs @@ -0,0 +1,69 @@ +// 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.IO; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + +namespace osu.Game.Skinning +{ + public class MaxDimensionLimitedTextureLoaderStore : IResourceStore + { + private readonly IResourceStore textureStore; + + public MaxDimensionLimitedTextureLoaderStore(IResourceStore textureStore) + { + this.textureStore = textureStore; + } + + public void Dispose() + { + textureStore?.Dispose(); + } + + public TextureUpload Get(string name) + { + var textureUpload = textureStore?.Get(name); + + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureUpload == null) + return null!; + + // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. + // To the point where GPUs cannot load the textures (along with most image editor apps). + // To work around this, let's look out for any stupid images and shrink them down into a usable size. + const int max_supported_texture_size = 8192; + + if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) + { + var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + + // The original texture upload will no longer be returned or used. + textureUpload.Dispose(); + + image.Mutate(i => i.Resize(new Size( + Math.Min(textureUpload.Width, max_supported_texture_size), + Math.Min(textureUpload.Height, max_supported_texture_size) + ))); + + return new TextureUpload(image); + } + + return textureUpload; + } + + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => textureStore?.GetAsync(name, cancellationToken); + + public Stream GetStream(string name) => textureStore?.GetStream(name) ?? default; + + public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources(); + } +} diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index c862a51870..cc302a8b1e 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -1,18 +1,17 @@ // 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.IO; using System.Linq; using System.Text; -using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -21,8 +20,6 @@ using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; using osu.Game.Screens.Play.HUD; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; namespace osu.Game.Skinning { @@ -31,12 +28,12 @@ namespace osu.Game.Skinning /// /// A texture store which can be used to perform user file lookups for this skin. /// - protected TextureStore? Textures { get; } + protected TextureStore Textures { get; } /// /// A sample store which can be used to perform user file lookups for this skin. /// - protected ISampleStore? Samples { get; } + protected ISampleStore Samples { get; } public readonly Live SkinInfo; @@ -46,17 +43,17 @@ namespace osu.Game.Skinning private readonly Dictionary drawableComponentInfo = new Dictionary(); - public abstract ISample? GetSample(ISampleInfo sampleInfo); + public abstract ISample GetSample(ISampleInfo sampleInfo); - public Texture? GetTexture(string componentName) => GetTexture(componentName, default, default); + public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); - public abstract Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT); + public abstract Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT); - public abstract IBindable? GetConfig(TLookup lookup) + public abstract IBindable GetConfig(TLookup lookup) where TLookup : notnull where TValue : notnull; - private readonly RealmBackedResourceStore? realmBackedStorage; + private readonly RealmBackedResourceStore realmBackedStorage; /// /// Construct a new skin. @@ -65,7 +62,7 @@ namespace osu.Game.Skinning /// Access to game-wide resources. /// An optional store which will *replace* all file lookups that are usually sourced from . /// 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? storage = null, string configurationFilename = @"skin.ini") + protected Skin(SkinInfo skin, IStorageResourceProvider resources, IResourceStore storage = null, string configurationFilename = @"skin.ini") { if (resources != null) { @@ -82,7 +79,7 @@ namespace osu.Game.Skinning (storage as ResourceStore)?.AddExtension("ogg"); Samples = samples; - Textures = new TextureStore(resources.Renderer, new SquishingTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); + Textures = new TextureStore(resources.Renderer, new MaxDimensionLimitedTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); } else { @@ -106,7 +103,7 @@ namespace osu.Game.Skinning { string filename = $"{skinnableTarget}.json"; - byte[]? bytes = storage?.Get(filename); + byte[] bytes = storage?.Get(filename); if (bytes == null) continue; @@ -159,7 +156,7 @@ namespace osu.Game.Skinning DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray(); } - public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup) + public virtual Drawable GetDrawableComponent(ISkinComponentLookup lookup) { switch (lookup) { @@ -216,56 +213,5 @@ namespace osu.Game.Skinning #endregion - public class SquishingTextureLoaderStore : IResourceStore - { - private readonly IResourceStore textureStore; - - public SquishingTextureLoaderStore(IResourceStore textureStore) - { - this.textureStore = textureStore; - } - - public void Dispose() - { - textureStore.Dispose(); - } - - public TextureUpload Get(string name) - { - var textureUpload = textureStore.Get(name); - - // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. - if (textureUpload.IsNull()) - return null!; - - // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. - // To the point where GPUs cannot load the textures (along with most image editor apps). - // To work around this, let's look out for any stupid images and shrink them down into a usable size. - const int max_supported_texture_size = 8192; - - if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) - { - var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); - - // The original texture upload will no longer be returned or used. - textureUpload.Dispose(); - - image.Mutate(i => i.Resize(new Size( - Math.Min(textureUpload.Width, max_supported_texture_size), - Math.Min(textureUpload.Height, max_supported_texture_size) - ))); - - return new TextureUpload(image); - } - - return textureUpload; - } - - public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => textureStore.GetAsync(name, cancellationToken); - - public Stream GetStream(string name) => textureStore.GetStream(name); - - public IEnumerable GetAvailableResources() => textureStore.GetAvailableResources(); - } } } From 8caf960f9ad5e4d1e92cbdef6dd75ad5dd520b63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 13:21:44 +0900 Subject: [PATCH 069/162] Revert weird nullable changes to `Skin.cs` --- osu.Game/Skinning/Skin.cs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index cc302a8b1e..419dacadfd 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.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.Diagnostics; @@ -28,12 +26,12 @@ namespace osu.Game.Skinning /// /// A texture store which can be used to perform user file lookups for this skin. /// - protected TextureStore Textures { get; } + protected TextureStore? Textures { get; } /// /// A sample store which can be used to perform user file lookups for this skin. /// - protected ISampleStore Samples { get; } + protected ISampleStore? Samples { get; } public readonly Live SkinInfo; @@ -43,17 +41,17 @@ namespace osu.Game.Skinning private readonly Dictionary drawableComponentInfo = new Dictionary(); - public abstract ISample GetSample(ISampleInfo sampleInfo); + public abstract ISample? GetSample(ISampleInfo sampleInfo); - public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); + public Texture? GetTexture(string componentName) => GetTexture(componentName, default, default); - public abstract Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT); + public abstract Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT); - public abstract IBindable GetConfig(TLookup lookup) + public abstract IBindable? GetConfig(TLookup lookup) where TLookup : notnull where TValue : notnull; - private readonly RealmBackedResourceStore realmBackedStorage; + private readonly RealmBackedResourceStore? realmBackedStorage; /// /// Construct a new skin. @@ -62,7 +60,7 @@ namespace osu.Game.Skinning /// Access to game-wide resources. /// An optional store which will *replace* all file lookups that are usually sourced from . /// 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 storage = null, string configurationFilename = @"skin.ini") + protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = @"skin.ini") { if (resources != null) { @@ -103,7 +101,7 @@ namespace osu.Game.Skinning { string filename = $"{skinnableTarget}.json"; - byte[] bytes = storage?.Get(filename); + byte[]? bytes = storage?.Get(filename); if (bytes == null) continue; @@ -156,7 +154,7 @@ namespace osu.Game.Skinning DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray(); } - public virtual Drawable GetDrawableComponent(ISkinComponentLookup lookup) + public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { switch (lookup) { @@ -212,6 +210,5 @@ namespace osu.Game.Skinning } #endregion - } } From 7bb6337d2ef8111784c7d7f4c6697748d5f6d53f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 13:24:45 +0900 Subject: [PATCH 070/162] Fix nullability (thanks bdach for patch) --- .../MaxDimensionLimitedTextureLoaderStore.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs index 94d7afaf7e..503add45ed 100644 --- a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs +++ b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.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.IO; @@ -17,9 +15,9 @@ namespace osu.Game.Skinning { public class MaxDimensionLimitedTextureLoaderStore : IResourceStore { - private readonly IResourceStore textureStore; + private readonly IResourceStore? textureStore; - public MaxDimensionLimitedTextureLoaderStore(IResourceStore textureStore) + public MaxDimensionLimitedTextureLoaderStore(IResourceStore? textureStore) { this.textureStore = textureStore; } @@ -60,10 +58,12 @@ namespace osu.Game.Skinning return textureUpload; } - public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => textureStore?.GetAsync(name, cancellationToken); + // TODO: remove null-forgiving operator below after texture stores are NRT-annotated framework-side + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + => textureStore?.GetAsync(name, cancellationToken) ?? Task.FromResult(null!); - public Stream GetStream(string name) => textureStore?.GetStream(name) ?? default; + public Stream? GetStream(string name) => textureStore?.GetStream(name); - public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources(); + public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty(); } } From 5bfd4e47a1b548626bee48e1a0b6edf6c148dd12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:59:54 +0900 Subject: [PATCH 071/162] Refactor position tracking touch handling (and comments) to read better --- .../UI/OsuTouchInputMapper.cs | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index 2e4f983fc4..7ff85f46a5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -59,27 +59,11 @@ namespace osu.Game.Rulesets.Osu.UI // This case gets special handling to allow for empty-space stream tapping. bool isDirectCircleTouch = osuInputManager.CheckScreenSpaceActionPressJudgeable(e.ScreenSpaceTouchDownPosition); - var trackedTouch = new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null, isDirectCircleTouch); + var newTouch = new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null, isDirectCircleTouch); - if (isDirectCircleTouch) - positionTrackingTouch = trackedTouch; - else - { - // If no direct touch is registered, we always use the new (latest) touch for positional tracking. - if (positionTrackingTouch?.DirectTouch != true) - positionTrackingTouch = trackedTouch; - else - { - // If not a direct circle touch, consider whether to release the an original direct touch. - if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction) - { - osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction); - positionTrackingTouch.Action = null; - } - } - } + updatePositionTracking(newTouch); - trackedTouches.Add(trackedTouch); + trackedTouches.Add(newTouch); // Important to update position before triggering the pressed action. handleTouchMovement(e); @@ -90,6 +74,43 @@ namespace osu.Game.Rulesets.Osu.UI return true; } + /// + /// Given a new touch, update the positional tracking state and any related operations. + /// + private void updatePositionTracking(TrackedTouch newTouch) + { + // If the new touch directly interacted with a circle's receptor, it always becomes the current touch for positional tracking. + if (newTouch.DirectTouch) + { + positionTrackingTouch = newTouch; + return; + } + + // Otherwise, we only want to use the new touch for position tracking if no other touch is tracking position yet.. + if (positionTrackingTouch == null) + { + positionTrackingTouch = newTouch; + return; + } + + // ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle) + if (!positionTrackingTouch.DirectTouch) + { + positionTrackingTouch = newTouch; + return; + } + + // In the case the new touch was not used for position tracking, we should also check the previous position tracking touch. + // If it was a direct touch and still has its action pressed, that action should be released. + // + // This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches. + if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction) + { + osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction); + positionTrackingTouch.Action = null; + } + } + private void handleTouchMovement(TouchEvent touchEvent) { // Movement should only be tracked for the most recent touch. From e4a79d8581cb52b9e813170d2a68cc205b7cc200 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 15:01:05 +0900 Subject: [PATCH 072/162] Rename test scene to differentiate from other ruleset touch tests --- .../{TestSceneTouchInput.cs => TestSceneOsuTouchInput.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneTouchInput.cs => TestSceneOsuTouchInput.cs} (99%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 4589eb88c0..72bcec6045 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -29,7 +29,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public partial class TestSceneTouchInput : OsuManualInputManagerTestScene + public partial class TestSceneOsuTouchInput : OsuManualInputManagerTestScene { [Resolved] private OsuConfigManager config { get; set; } = null!; From 1cde90d55d8c8a22152dcd0f1cfe291b8b334b58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 15:03:47 +0900 Subject: [PATCH 073/162] Add note about `CheckScreenSpaceActionPresJudgeable` being naive --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 465dd13362..ccd388192e 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -43,6 +43,10 @@ namespace osu.Game.Rulesets.Osu => new OsuKeyBindingContainer(ruleset, variant, unique); public bool CheckScreenSpaceActionPressJudgeable(Vector2 screenSpacePosition) => + // This is a very naive but simple approach. + // + // Based on user feedback of more nuanced scenarios (where touch doesn't behave as expected), + // this can be expanded to a more complex implementation, but I'd still want to keep it as simple as we can. NonPositionalInputQueue.OfType().Any(c => c.ReceivePositionalInputAt(screenSpacePosition)); public OsuInputManager(RulesetInfo ruleset) From 9499d3a20a9b5fefff77672410107a9c44ac2392 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 15:50:49 +0900 Subject: [PATCH 074/162] Add support for disabling "hit lighting" with osu! argon skin --- .../TestSceneHitCircle.cs | 12 +++++++ .../Skinning/Argon/ArgonMainCirclePiece.cs | 36 ++++++++++++++----- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index a418df605f..50f9c5e775 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -5,9 +5,11 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; + [Resolved] + private OsuConfigManager config { get; set; } + [Test] public void TestHits() { @@ -56,6 +61,13 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Hit stream late", () => SetContents(_ => testStream(5, true, 150))); } + [Test] + public void TestHitLighting() + { + AddToggleStep("toggle hit lighting", v => config.SetValue(OsuSetting.HitLighting, v)); + AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true))); + } + private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) { var playfield = new TestOsuPlayfield(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index db458ec48a..ab24f9402a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; @@ -44,6 +45,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly IBindable indexInCurrentCombo = new Bindable(); private readonly FlashPiece flash; + private Bindable configHitLighting = null!; + [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; @@ -96,12 +99,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { var drawableOsuObject = (DrawableOsuHitObject)drawableObject; accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); + + configHitLighting = config.GetBindable(OsuSetting.HitLighting); } protected override void LoadComplete() @@ -140,12 +145,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case ArmedState.Hit: // Fade out time is at a maximum of 800. Must match `DrawableHitCircle`'s arbitrary lifetime spec. const double fade_out_time = 800; - const double flash_in_duration = 150; const double resize_duration = 400; const float shrink_size = 0.8f; + // When the user has hit lighting disabled, we won't be showing the bright white flash. + // To make things look good, the surrounding animations are also slightly adjusted. + bool showFlash = configHitLighting.Value; + // Animating with the number present is distracting. // The number disappearing is hidden by the bright flash. number.FadeOut(flash_in_duration / 2); @@ -176,15 +184,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon using (BeginDelayedSequence(flash_in_duration / 12)) { outerGradient.ResizeTo(OUTER_GRADIENT_SIZE * shrink_size, resize_duration, Easing.OutElasticHalf); - outerGradient - .FadeColour(Color4.White, 80) - .Then() - .FadeOut(flash_in_duration); + + if (showFlash) + { + outerGradient + .FadeColour(Color4.White, 80) + .Then() + .FadeOut(flash_in_duration); + } + else + { + outerGradient + .FadeColour(Color4.White, flash_in_duration * 8) + .FadeOut(flash_in_duration * 2); + } } - flash.FadeTo(1, flash_in_duration, Easing.OutQuint); + if (showFlash) + flash.FadeTo(1, flash_in_duration, Easing.OutQuint); + + this.FadeOut(showFlash ? fade_out_time : fade_out_time / 2, Easing.OutQuad); - this.FadeOut(fade_out_time, Easing.OutQuad); break; } } From f81dea4166baf3bc7d0816d82df62c2efac8e075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 16:24:52 +0900 Subject: [PATCH 075/162] Remove now unused localisation string --- osu.Game/Localisation/UserInterfaceStrings.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index fb9eeeb3de..ea664d7b50 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -109,11 +109,6 @@ namespace osu.Game.Localisation /// public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); - /// - /// "Background blur" - /// - public static LocalisableString BackgroundBlurLevel => new TranslatableString(getKey(@"background_blur_level"), @"Background blur"); - /// /// "no limit" /// From 1a9ed1ac3fa56473f5b2aaa586ba7f747de291ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 16:28:06 +0900 Subject: [PATCH 076/162] Remove unnecessary `IgnoreUserSettings` value change --- osu.Game/Screens/Select/SongSelect.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 505f932bff..680c740b01 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -133,15 +133,11 @@ namespace osu.Game.Screens.Select configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); configBackgroundBlur.BindValueChanged(e => { - if (this.IsCurrentScreen()) - { - ApplyToBackground(background => - { - background.IgnoreUserSettings.Value = true; - background.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0; - }); - } - }, true); + if (!this.IsCurrentScreen()) + return; + + ApplyToBackground(b => b.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0); + }); LoadComponentAsync(Carousel = new BeatmapCarousel { From e333e12b2ec1e0d5985c57d6ad94b858627690fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 16:28:38 +0900 Subject: [PATCH 077/162] Fix typo in settings enum (seriously) --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index fff5dc62c6..1c1745b55d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); - SetDefault(OsuSetting.SongSelectBackgoundBlur, true); + SetDefault(OsuSetting.SongSelectBackgroundBlur, true); // Online settings SetDefault(OsuSetting.Username, string.Empty); @@ -341,7 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, - SongSelectBackgoundBlur, + SongSelectBackgroundBlur, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index d40d24a528..6f30fcd100 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface new SettingsCheckbox { LabelText = GameplaySettingsStrings.BackgroundBlur, - Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlur) + Current = config.GetBindable(OsuSetting.SongSelectBackgroundBlur) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 680c740b01..41e722ae88 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); + configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgroundBlur); configBackgroundBlur.BindValueChanged(e => { if (!this.IsCurrentScreen()) From 01e280eb6bba20f85b49e98379550ad20906822c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 16:31:41 +0900 Subject: [PATCH 078/162] Add classic default for song select blur setting --- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 6f30fcd100..d3303e409c 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -46,7 +46,8 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface new SettingsCheckbox { LabelText = GameplaySettingsStrings.BackgroundBlur, - Current = config.GetBindable(OsuSetting.SongSelectBackgroundBlur) + Current = config.GetBindable(OsuSetting.SongSelectBackgroundBlur), + ClassicDefault = false, } }; } From d15f8c2f3a1ffb79fb1fcfd2e0ee2717b2c0cbb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 17:11:14 +0900 Subject: [PATCH 079/162] Fix beatmaps with multiple `osb` files potentially reading the storyboard from the wrong one In stable, the storyboard filename is fixed. In lazer, we were always looking for the first `.osb` in the database. In the case a beatmap archive housed more than one `.osb` files, this may load the incorrect one. Using `GetDisplayString` here feels like it could potentially go wrong in the future, so I'm open to hard-coding this locally (or using string manipulation to remove the ` [creator_name]` portion of the beatmap's filename). Open to opinions on that. Fixes storyboard playback in https://osu.ppy.sh/beatmapsets/1913687#osu/3947758 --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index e6f96330e7..546f0e6c3a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -20,6 +20,7 @@ using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -268,7 +269,7 @@ namespace osu.Game.Beatmaps Stream storyboardFileStream = null; - if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename) + if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals($"{BeatmapSetInfo.GetDisplayString()}.osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename) { string storyboardFileStorePath = BeatmapSetInfo?.GetPathForFile(storyboardFilename); storyboardFileStream = GetStream(storyboardFileStorePath); From d09d6f31d704c2b69ecdef00579f80261dc7a2e0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 25 Jan 2023 12:20:51 +0300 Subject: [PATCH 080/162] Implement Masking property for TrianglesBackground --- .../Skinning/Default/TrianglesPiece.cs | 1 + .../TestSceneTrianglesBackground.cs | 7 ++- osu.Game/Graphics/Backgrounds/Triangles.cs | 43 +++++++++++++++---- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs index f1143cf14d..16cd302b88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; + Masking = false; } protected override void Update() diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs index 8a5489f476..378dd99664 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs @@ -5,6 +5,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Framework.Graphics; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osuTK; namespace osu.Game.Tests.Visual.Background { @@ -25,7 +26,10 @@ namespace osu.Game.Tests.Visual.Background { RelativeSizeAxes = Axes.Both, ColourLight = Color4.White, - ColourDark = Color4.Gray + ColourDark = Color4.Gray, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.9f) } }; } @@ -36,6 +40,7 @@ namespace osu.Game.Tests.Visual.Background AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s); AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s)); + AddToggleStep("Masking", m => triangles.Masking = m); } } } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index d9dff1574f..1fbf9b4dfa 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -77,6 +77,12 @@ namespace osu.Game.Graphics.Backgrounds set => triangleScale.Value = value; } + /// + /// If enabled, only the portion of triangles that falls within this 's + /// shape is drawn to the screen. + /// + public bool Masking { get; set; } = true; + /// /// Whether we should drop-off alpha values of triangles more quickly to improve /// the visual appearance of fading. This defaults to on as it is generally more @@ -252,6 +258,7 @@ namespace osu.Game.Graphics.Backgrounds private IShader shader; private Texture texture; + private bool masking; private readonly List parts = new List(); private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; @@ -271,6 +278,7 @@ namespace osu.Game.Graphics.Backgrounds shader = Source.shader; texture = Source.texture; size = Source.DrawSize; + masking = Source.Masking; parts.Clear(); parts.AddRange(Source.parts); @@ -294,26 +302,45 @@ namespace osu.Game.Graphics.Backgrounds Vector2 relativeSize = Vector2.Divide(triangleSize * particle.Scale, size); Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); - Vector2 topRight = topLeft + new Vector2(relativeSize.X, 0f); - Vector2 bottomLeft = topLeft + new Vector2(0f, relativeSize.Y); - Vector2 bottomRight = bottomLeft + new Vector2(relativeSize.X, 0f); + + Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); var drawQuad = new Quad( - Vector2Extensions.Transform(topLeft * size, DrawInfo.Matrix), - Vector2Extensions.Transform(topRight * size, DrawInfo.Matrix), - Vector2Extensions.Transform(bottomLeft * size, DrawInfo.Matrix), - Vector2Extensions.Transform(bottomRight * size, DrawInfo.Matrix) + Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.TopRight * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.BottomLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.BottomRight * size, DrawInfo.Matrix) ); ColourInfo colourInfo = DrawColourInfo.Colour; colourInfo.ApplyChild(particle.Colour); - renderer.DrawQuad(texture, drawQuad, colourInfo, vertexAction: vertexBatch.AddAction); + RectangleF textureCoords = new RectangleF( + triangleQuad.TopLeft.X - topLeft.X, + triangleQuad.TopLeft.Y - topLeft.Y, + triangleQuad.Width, + triangleQuad.Height + ) / relativeSize; + + renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords); } shader.Unbind(); } + private static Quad clampToDrawable(Vector2 topLeft, Vector2 size) + { + float leftClamped = Math.Clamp(topLeft.X, 0f, 1f); + float topClamped = Math.Clamp(topLeft.Y, 0f, 1f); + + return new Quad( + leftClamped, + topClamped, + Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped, + Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped + ); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 78cfe2f5476ec361f46f93c6376ba46188273093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 18:46:52 +0900 Subject: [PATCH 081/162] Fix hit circle kiai test scene not always working as expected --- .../TestSceneHitCircleKiai.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs index 2c9f1acd2c..718664d649 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs @@ -4,29 +4,38 @@ #nullable disable using NUnit.Framework; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public partial class TestSceneHitCircleKiai : TestSceneHitCircle + public partial class TestSceneHitCircleKiai : TestSceneHitCircle, IBeatSyncProvider { + private ControlPointInfo controlPoints { get; set; } + [SetUp] public void SetUp() => Schedule(() => { - var controlPointInfo = new ControlPointInfo(); + controlPoints = new ControlPointInfo(); - controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); - controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + controlPoints.Add(0, new EffectControlPoint { KiaiMode = true }); Beatmap.Value = CreateWorkingBeatmap(new Beatmap { - ControlPointInfo = controlPointInfo + ControlPointInfo = controlPoints }); // track needs to be playing for BeatSyncedContainer to work. Beatmap.Value.Track.Start(); }); + + ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => new ChannelAmplitudes(); + ControlPointInfo IBeatSyncProvider.ControlPoints => controlPoints; + IClock IBeatSyncProvider.Clock => Clock; } } From 1485a6e00670118ff29dd79912b3ddc29f22a773 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 25 Jan 2023 12:54:30 +0300 Subject: [PATCH 082/162] Make masking false by default --- osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs | 1 - osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs index 16cd302b88..f1143cf14d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; - Masking = false; } protected override void Update() diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 1fbf9b4dfa..6750d74a08 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -81,7 +81,7 @@ namespace osu.Game.Graphics.Backgrounds /// If enabled, only the portion of triangles that falls within this 's /// shape is drawn to the screen. /// - public bool Masking { get; set; } = true; + public bool Masking { get; set; } /// /// Whether we should drop-off alpha values of triangles more quickly to improve From 48d68b0f4f385ca862db266011cb9fffcb5efee7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 18:59:26 +0900 Subject: [PATCH 083/162] Add very basic kiai flash to argon hit circles --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index db458ec48a..d50a3eba13 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly IBindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); private readonly FlashPiece flash; + private readonly KiaiFlash kiaiFlash; [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; @@ -82,6 +83,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + new CircularContainer + { + Masking = true, + Size = Size, + Child = kiaiFlash = new KiaiFlash + { + RelativeSizeAxes = Axes.Both, + } + }, number = new OsuSpriteText { Font = OsuFont.Default.With(size: 52, weight: FontWeight.Bold), @@ -117,6 +127,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon outerGradient.ClearTransforms(targetMember: nameof(Colour)); outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); + kiaiFlash.Colour = colour.NewValue; outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); flash.Colour = colour.NewValue; From a4a94cb96e418cbd6adaffd0e7be75c08636616c Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Wed, 25 Jan 2023 14:34:00 -0300 Subject: [PATCH 084/162] Add movement to misses in osu ruleset --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 5 ++--- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 2430e511c4..7fece3e896 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -104,9 +104,8 @@ namespace osu.Game.Skinning this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - //todo: this only applies to osu! ruleset apparently. - this.MoveTo(new Vector2(0, -2)); - this.MoveToOffset(new Vector2(0, 20), fade_out_delay + fade_out_length, Easing.In); + this.MoveTo(new Vector2(0, -5)); + this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); float rotation = RNG.NextSingle(-8.6f, 8.6f); diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 796080f4f5..291b28991d 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osuTK; + namespace osu.Game.Skinning { @@ -55,6 +57,9 @@ namespace osu.Game.Skinning this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); + this.MoveTo(new Vector2(0, -5)); + this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); + float rotation = RNG.NextSingle(-8.6f, 8.6f); this.RotateTo(0); From fc968d1d89887474093ef017c2346777252b4d6c Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Wed, 25 Jan 2023 14:38:02 -0300 Subject: [PATCH 085/162] del extra newline --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 291b28991d..749edf80db 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; - namespace osu.Game.Skinning { public partial class LegacyJudgementPieceOld : CompositeDrawable, IAnimatableJudgement From fd495e87f74d5e6b76d67b9fa2865378b95c562b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Jan 2023 18:37:58 +0100 Subject: [PATCH 086/162] Fix `GetAsync()` not limiting texture dimensions --- .../MaxDimensionLimitedTextureLoaderStore.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs index 503add45ed..f15097a169 100644 --- a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs +++ b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs @@ -35,6 +35,25 @@ namespace osu.Game.Skinning if (textureUpload == null) return null!; + return limitTextureUploadSize(textureUpload); + } + + public async Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + { + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureStore == null) + return null!; + + var textureUpload = await textureStore.GetAsync(name, cancellationToken).ConfigureAwait(false); + + if (textureUpload == null) + return null!; + + return await Task.Run(() => limitTextureUploadSize(textureUpload), cancellationToken).ConfigureAwait(false); + } + + private TextureUpload limitTextureUploadSize(TextureUpload textureUpload) + { // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. // To the point where GPUs cannot load the textures (along with most image editor apps). // To work around this, let's look out for any stupid images and shrink them down into a usable size. @@ -58,10 +77,6 @@ namespace osu.Game.Skinning return textureUpload; } - // TODO: remove null-forgiving operator below after texture stores are NRT-annotated framework-side - public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) - => textureStore?.GetAsync(name, cancellationToken) ?? Task.FromResult(null!); - public Stream? GetStream(string name) => textureStore?.GetStream(name); public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty(); From 34aa8b872eb38a460b2a726d2f689a5da96c80c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Jan 2023 18:39:26 +0100 Subject: [PATCH 087/162] Mention stable weirdness next to constant --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index 6887674a1f..05ba2b8f22 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy d.RelativeSizeAxes = Axes.Both; d.Size = Vector2.One; d.FillMode = FillMode.Stretch; - d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; + d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; // constant matching stable. // Todo: Wrap? }); From a6fc3ce477e400ef30298ef00cd21330d0849b6c Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Wed, 25 Jan 2023 20:38:55 -0300 Subject: [PATCH 088/162] bring comment back --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 7fece3e896..6be310c419 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -104,6 +104,7 @@ namespace osu.Game.Skinning this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); + //todo: this only applies to osu! ruleset apparently. this.MoveTo(new Vector2(0, -5)); this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); From bcecc49092d08b0f1a775e8fe0a092f382edc491 Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Wed, 25 Jan 2023 23:05:11 -0300 Subject: [PATCH 089/162] Only do misses animations on modern skins --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 21 ++++---------------- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 11 +++++++--- osu.Game/Skinning/LegacySkin.cs | 4 ++-- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 6be310c419..5e2bba9ccd 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -17,6 +17,7 @@ namespace osu.Game.Skinning public partial class LegacyJudgementPieceNew : CompositeDrawable, IAnimatableJudgement { private readonly HitResult result; + private readonly decimal? version; private readonly LegacyJudgementPieceOld? temporaryOldStyle; @@ -24,9 +25,10 @@ namespace osu.Game.Skinning private readonly ParticleExplosion? particles; - public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Texture? particleTexture) + public LegacyJudgementPieceNew(HitResult result, decimal? version, Func createMainDrawable, Texture? particleTexture) { this.result = result; + this.version = version; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; @@ -54,7 +56,7 @@ namespace osu.Game.Skinning if (result != HitResult.Miss) { //new judgement shows old as a temporary effect - AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f, true) + AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, this.version, createMainDrawable, 1.05f, true) { Blending = BlendingParameters.Additive, Anchor = Anchor.Centre, @@ -100,21 +102,6 @@ namespace osu.Game.Skinning switch (result) { - case HitResult.Miss: - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); - - //todo: this only applies to osu! ruleset apparently. - this.MoveTo(new Vector2(0, -5)); - this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); - - float rotation = RNG.NextSingle(-8.6f, 8.6f); - - this.RotateTo(0); - this.RotateTo(rotation, fade_in_length) - .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); - break; - default: mainPiece.ScaleTo(0.9f); mainPiece.ScaleTo(1.05f, fade_out_delay + fade_out_length); diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 749edf80db..e427e19a6b 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -17,13 +17,15 @@ namespace osu.Game.Skinning public partial class LegacyJudgementPieceOld : CompositeDrawable, IAnimatableJudgement { private readonly HitResult result; + private readonly decimal? version; private readonly float finalScale; private readonly bool forceTransforms; - public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f, bool forceTransforms = false) + public LegacyJudgementPieceOld(HitResult result, decimal? version, Func createMainDrawable, float finalScale = 1f, bool forceTransforms = false) { this.result = result; + this.version = version; this.finalScale = finalScale; this.forceTransforms = forceTransforms; @@ -56,8 +58,11 @@ namespace osu.Game.Skinning this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - this.MoveTo(new Vector2(0, -5)); - this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); + if (this.version > 1) + { + this.MoveTo(new Vector2(0, -5)); + this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); + } float rotation = RNG.NextSingle(-8.6f, 8.6f); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5f12d2ce23..0942966b1c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -390,9 +390,9 @@ namespace osu.Game.Skinning var particle = getParticleTexture(resultComponent.Component); if (particle != null) - return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, particle); + return new LegacyJudgementPieceNew(resultComponent.Component, this.Configuration.LegacyVersion, createDrawable, particle); - return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); + return new LegacyJudgementPieceOld(resultComponent.Component, this.Configuration.LegacyVersion, createDrawable); } return null; From 7cc4fd4efc9fce06a9705152234bd12c7657c3c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 15:09:36 +0900 Subject: [PATCH 090/162] Use the exact method stable uses for generating storyboard filenames --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 33 ++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 546f0e6c3a..76a31a6f78 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -20,7 +20,6 @@ using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; -using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -269,7 +268,10 @@ namespace osu.Game.Beatmaps Stream storyboardFileStream = null; - if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals($"{BeatmapSetInfo.GetDisplayString()}.osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename) + string mainStoryboardFilename = getMainStoryboardFilename(BeatmapSetInfo.Metadata); + + if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals(mainStoryboardFilename, StringComparison.OrdinalIgnoreCase))?.Filename is string + storyboardFilename) { string storyboardFileStorePath = BeatmapSetInfo?.GetPathForFile(storyboardFilename); storyboardFileStream = GetStream(storyboardFileStorePath); @@ -313,6 +315,33 @@ namespace osu.Game.Beatmaps } public override Stream GetStream(string storagePath) => resources.Files.GetStream(storagePath); + + private string getMainStoryboardFilename(IBeatmapMetadataInfo metadata) + { + // Matches stable implementation, because it's probably simpler than trying to do anything else. + // This may need to be reconsidered after we begin storing storyboards in the new editor. + return windowsFilenameStrip( + (metadata.Artist.Length > 0 ? metadata.Artist + @" - " + metadata.Title : Path.GetFileNameWithoutExtension(metadata.AudioFile)) + + (metadata.Author.Username.Length > 0 ? @" (" + metadata.Author.Username + @")" : string.Empty) + + @".osb"); + + string windowsFilenameStrip(string entry) + { + // Inlined from Path.GetInvalidFilenameChars() to ensure the windows characters are used (to match stable). + char[] invalidCharacters = + { + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', + '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', + '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', + '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' + }; + + foreach (char c in invalidCharacters) + entry = entry.Replace(c.ToString(), string.Empty); + + return entry; + } + } } } } From de1d473d2908527a903f12fed42713b359aac81a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 15:24:10 +0900 Subject: [PATCH 091/162] Fix kiai flash being visible and incorrectly sized during hit animation --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 04a4d4bede..e7e6e26b5e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly IBindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); private readonly FlashPiece flash; - private readonly KiaiFlash kiaiFlash; + private readonly Container kiaiContainer; private Bindable configHitLighting = null!; @@ -83,11 +83,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new CircularContainer + kiaiContainer = new CircularContainer { Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = Size, - Child = kiaiFlash = new KiaiFlash + Child = new KiaiFlash { RelativeSizeAxes = Axes.Both, } @@ -129,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon outerGradient.ClearTransforms(targetMember: nameof(Colour)); outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); - kiaiFlash.Colour = colour.NewValue; + kiaiContainer.Colour = colour.NewValue; outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); flash.Colour = colour.NewValue; @@ -191,6 +193,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // gradient layers. border.ResizeTo(Size * shrink_size + new Vector2(border.BorderThickness), resize_duration, Easing.OutElasticHalf); + // Kiai flash should track the overall size but also be cleaned up quite fast, so we don't get additional + // flashes after the hit animation is already in a mostly-completed state. + kiaiContainer.ResizeTo(Size * shrink_size, resize_duration, Easing.OutElasticHalf); + kiaiContainer.FadeOut(flash_in_duration, Easing.OutQuint); + // The outer gradient is resize with a slight delay from the border. // This is to give it a bomb-like effect, with the border "triggering" its animation when getting close. using (BeginDelayedSequence(flash_in_duration / 12)) From f3c92749bf280c10f5a9ed971bb92f55b7848b48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 15:43:03 +0900 Subject: [PATCH 092/162] Fix code quality issues --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 5 +---- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 5e2bba9ccd..2f3a308b57 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; -using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -17,7 +16,6 @@ namespace osu.Game.Skinning public partial class LegacyJudgementPieceNew : CompositeDrawable, IAnimatableJudgement { private readonly HitResult result; - private readonly decimal? version; private readonly LegacyJudgementPieceOld? temporaryOldStyle; @@ -28,7 +26,6 @@ namespace osu.Game.Skinning public LegacyJudgementPieceNew(HitResult result, decimal? version, Func createMainDrawable, Texture? particleTexture) { this.result = result; - this.version = version; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; @@ -56,7 +53,7 @@ namespace osu.Game.Skinning if (result != HitResult.Miss) { //new judgement shows old as a temporary effect - AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, this.version, createMainDrawable, 1.05f, true) + AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, version, createMainDrawable, 1.05f, true) { Blending = BlendingParameters.Additive, Anchor = Anchor.Centre, diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index e427e19a6b..ed3a88219e 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -58,7 +58,7 @@ namespace osu.Game.Skinning this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - if (this.version > 1) + if (version > 1) { this.MoveTo(new Vector2(0, -5)); this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0942966b1c..04bb551668 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -390,9 +390,9 @@ namespace osu.Game.Skinning var particle = getParticleTexture(resultComponent.Component); if (particle != null) - return new LegacyJudgementPieceNew(resultComponent.Component, this.Configuration.LegacyVersion, createDrawable, particle); + return new LegacyJudgementPieceNew(resultComponent.Component, Configuration.LegacyVersion, createDrawable, particle); - return new LegacyJudgementPieceOld(resultComponent.Component, this.Configuration.LegacyVersion, createDrawable); + return new LegacyJudgementPieceOld(resultComponent.Component, Configuration.LegacyVersion, createDrawable); } return null; From 7cd21e12f3278d8da8e8fc1378d20e0393a6cef5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 26 Jan 2023 09:46:41 +0300 Subject: [PATCH 093/162] Implement masking property for TrianglesV2 background --- .../TestSceneTrianglesV2Background.cs | 58 +++++++++++++++++-- osu.Game/Graphics/Backgrounds/TrianglesV2.cs | 50 ++++++++++++---- 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs index ae1f3de6bf..01a2464b8e 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs @@ -8,12 +8,14 @@ using osuTK; using osuTK.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Framework.Graphics.Colour; +using osu.Game.Graphics.Sprites; namespace osu.Game.Tests.Visual.Background { public partial class TestSceneTrianglesV2Background : OsuTestScene { private readonly TrianglesV2 triangles; + private readonly TrianglesV2 maskedTriangles; private readonly Box box; public TestSceneTrianglesV2Background() @@ -31,12 +33,20 @@ namespace osu.Game.Tests.Visual.Background Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + Spacing = new Vector2(0, 10), Children = new Drawable[] { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Masked" + }, new Container { Size = new Vector2(500, 100), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Masking = true, CornerRadius = 40, Children = new Drawable[] @@ -54,9 +64,43 @@ namespace osu.Game.Tests.Visual.Background } } }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Non-masked" + }, new Container { Size = new Vector2(500, 100), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red + }, + maskedTriangles = new TrianglesV2 + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + } + } + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Gradient comparison box" + }, + new Container + { + Size = new Vector2(500, 100), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Masking = true, CornerRadius = 40, Child = box = new Box @@ -75,14 +119,16 @@ namespace osu.Game.Tests.Visual.Background AddSliderStep("Spawn ratio", 0f, 10f, 1f, s => { - triangles.SpawnRatio = s; + triangles.SpawnRatio = maskedTriangles.SpawnRatio = s; triangles.Reset(1234); + maskedTriangles.Reset(1234); }); - AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = t); + AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = maskedTriangles.Thickness = t); - AddStep("White colour", () => box.Colour = triangles.Colour = Color4.White); - AddStep("Vertical gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red)); - AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red)); + AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White); + AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red)); + AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red)); + AddToggleStep("Masking", m => maskedTriangles.Masking = m); } } } diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index d543f082b4..3bc8bb6df1 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -34,6 +34,12 @@ namespace osu.Game.Graphics.Backgrounds /// protected virtual bool CreateNewTriangles => true; + /// + /// If enabled, only the portion of triangles that falls within this 's + /// shape is drawn to the screen. + /// + public bool Masking { get; set; } + private readonly BindableFloat spawnRatio = new BindableFloat(1f); /// @@ -189,6 +195,7 @@ namespace osu.Game.Graphics.Backgrounds private Vector2 size; private float thickness; private float texelSize; + private bool masking; private IVertexBatch? vertexBatch; @@ -205,6 +212,7 @@ namespace osu.Game.Graphics.Backgrounds texture = Source.texture; size = Source.DrawSize; thickness = Source.Thickness; + masking = Source.Masking; Quad triangleQuad = new Quad( Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix), @@ -236,26 +244,31 @@ namespace osu.Game.Graphics.Backgrounds shader.GetUniform("thickness").UpdateValue(ref thickness); shader.GetUniform("texelSize").UpdateValue(ref texelSize); - float relativeHeight = triangleSize.Y / size.Y; - float relativeWidth = triangleSize.X / size.X; + Vector2 relativeSize = Vector2.Divide(triangleSize, size); foreach (TriangleParticle particle in parts) { - Vector2 topLeft = particle.Position - new Vector2(relativeWidth * 0.5f, 0f); - Vector2 topRight = topLeft + new Vector2(relativeWidth, 0f); - Vector2 bottomLeft = topLeft + new Vector2(0f, relativeHeight); - Vector2 bottomRight = bottomLeft + new Vector2(relativeWidth, 0f); + Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); + + Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); var drawQuad = new Quad( - Vector2Extensions.Transform(topLeft * size, DrawInfo.Matrix), - Vector2Extensions.Transform(topRight * size, DrawInfo.Matrix), - Vector2Extensions.Transform(bottomLeft * size, DrawInfo.Matrix), - Vector2Extensions.Transform(bottomRight * size, DrawInfo.Matrix) + Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.TopRight * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.BottomLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.BottomRight * size, DrawInfo.Matrix) ); - ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, new Quad(topLeft, topRight, bottomLeft, bottomRight)); + ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, triangleQuad); - renderer.DrawQuad(texture, drawQuad, colourInfo, vertexAction: vertexBatch.AddAction); + RectangleF textureCoords = new RectangleF( + triangleQuad.TopLeft.X - topLeft.X, + triangleQuad.TopLeft.Y - topLeft.Y, + triangleQuad.Width, + triangleQuad.Height + ) / relativeSize; + + renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords); } shader.Unbind(); @@ -272,6 +285,19 @@ namespace osu.Game.Graphics.Backgrounds }; } + private static Quad clampToDrawable(Vector2 topLeft, Vector2 size) + { + float leftClamped = Math.Clamp(topLeft.X, 0f, 1f); + float topClamped = Math.Clamp(topLeft.Y, 0f, 1f); + + return new Quad( + leftClamped, + topClamped, + Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped, + Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped + ); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From d63719a602b3c5ef397d6cf6017cfc31a34a1476 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 16:04:56 +0900 Subject: [PATCH 094/162] Move and rename the base component class --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- osu.Game/Skinning/Components/TextElement.cs | 2 +- ...ultTextSkinComponent.cs => FontAdjustableSkinComponent.cs} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Skinning/{Components/DefaultTextSkinComponent.cs => FontAdjustableSkinComponent.cs} (88%) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 5696158d49..68bb1e7ddc 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -23,7 +23,7 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public partial class BeatmapAttributeText : DefaultTextSkinComponent + public partial class BeatmapAttributeText : FontAdjustableSkinComponent { [SettingSource("Attribute", "The attribute to be displayed.")] public Bindable Attribute { get; } = new Bindable(BeatmapAttribute.StarRating); diff --git a/osu.Game/Skinning/Components/TextElement.cs b/osu.Game/Skinning/Components/TextElement.cs index fb779fdb83..d87fb125bb 100644 --- a/osu.Game/Skinning/Components/TextElement.cs +++ b/osu.Game/Skinning/Components/TextElement.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public partial class TextElement : DefaultTextSkinComponent + public partial class TextElement : FontAdjustableSkinComponent { [SettingSource("Text", "The text to be displayed.")] public Bindable Text { get; } = new Bindable("Circles!"); diff --git a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs b/osu.Game/Skinning/FontAdjustableSkinComponent.cs similarity index 88% rename from osu.Game/Skinning/Components/DefaultTextSkinComponent.cs rename to osu.Game/Skinning/FontAdjustableSkinComponent.cs index abe16918c5..716b472ba5 100644 --- a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs +++ b/osu.Game/Skinning/FontAdjustableSkinComponent.cs @@ -7,12 +7,12 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; -namespace osu.Game.Skinning.Components +namespace osu.Game.Skinning { /// /// Skin element that contains text and have ability to control its font. /// - public abstract partial class DefaultTextSkinComponent : Container, ISkinnableDrawable + public abstract partial class FontAdjustableSkinComponent : Container, ISkinnableDrawable { public bool UsesFixedAnchor { get; set; } From 64e7f6f138a5ae087f785b736248aba009b6bfe7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 16:05:07 +0900 Subject: [PATCH 095/162] Add more documentation around the implementation of `FontAdjustableSkinComponent` --- osu.Game/Skinning/FontAdjustableSkinComponent.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/FontAdjustableSkinComponent.cs b/osu.Game/Skinning/FontAdjustableSkinComponent.cs index 716b472ba5..ee73417bfe 100644 --- a/osu.Game/Skinning/FontAdjustableSkinComponent.cs +++ b/osu.Game/Skinning/FontAdjustableSkinComponent.cs @@ -10,23 +10,30 @@ using osu.Game.Graphics; namespace osu.Game.Skinning { /// - /// Skin element that contains text and have ability to control its font. + /// A skin component that contains text and allows the user to choose its font. /// public abstract partial class FontAdjustableSkinComponent : Container, ISkinnableDrawable { public bool UsesFixedAnchor { get; set; } - [SettingSource("Font", "Font to use.")] + [SettingSource("Font", "The font to use.")] public Bindable Font { get; } = new Bindable(Typeface.Torus); + /// + /// Implement to apply the user font selection to one or more components. + /// protected abstract void SetFont(FontUsage font); protected override void LoadComplete() { base.LoadComplete(); + Font.BindValueChanged(e => { - FontUsage f = OsuFont.GetFont(e.NewValue, weight: e.NewValue == Typeface.Venera ? FontWeight.Bold : FontWeight.Regular); + // We only have bold weight for venera, so let's force that. + FontWeight fontWeight = e.NewValue == Typeface.Venera ? FontWeight.Bold : FontWeight.Regular; + + FontUsage f = OsuFont.GetFont(e.NewValue, weight: fontWeight); SetFont(f); }, true); } From e5d4979bc3942aa564a758bf5e09331dcc3ebdcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 16:56:37 +0900 Subject: [PATCH 096/162] Fix default health bar having a very weird anchor point in the skin editor --- .../Screens/Play/HUD/DefaultHealthDisplay.cs | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index 76027f9e5d..62d66efb33 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -78,30 +78,39 @@ namespace osu.Game.Screens.Play.HUD public DefaultHealthDisplay() { - Size = new Vector2(1, 5); - RelativeSizeAxes = Axes.X; - Margin = new MarginPadding { Top = 20 }; + const float padding = 20; + const float bar_height = 5; - InternalChildren = new Drawable[] + Size = new Vector2(1, bar_height + padding * 2); + RelativeSizeAxes = Axes.X; + + InternalChild = new Container { - new Box + Padding = new MarginPadding { Vertical = padding }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - fill = new Container - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0, 1), - Masking = true, - Children = new[] + new Box { - new Box + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + fill = new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0, 1), + Masking = true, + Children = new[] { - RelativeSizeAxes = Axes.Both, + new Box + { + RelativeSizeAxes = Axes.Both, + } } - } - }, + }, + } }; } From 7344d34d5b59ef6c9517478df3903482a77ad106 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 17:12:41 +0900 Subject: [PATCH 097/162] Move `where` class specs to next line --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 3 ++- .../Sliders/Components/PathControlPointVisualiser.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index a4d5c08b8a..12e5ca0236 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// A visualisation of a single in an osu hit object with a path. /// /// The type of which this visualises. - public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip where T : OsuHitObject, IHasPath + public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip + where T : OsuHitObject, IHasPath { public Action, MouseButtonEvent> RequestSelection; 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 0310239052..17d0fc457a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -29,7 +29,8 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu where T : OsuHitObject, IHasPath + public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + where T : OsuHitObject, IHasPath { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield. From 2017ac11358c489eeda992c176e30acdbfeeca33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 17:46:19 +0900 Subject: [PATCH 098/162] Apply NRT to all skin editor classes --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 10 ++-- .../Skinning/Editor/SkinBlueprintContainer.cs | 6 +-- osu.Game/Skinning/Editor/SkinEditor.cs | 54 +++++++++++-------- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 22 ++++---- .../Skinning/Editor/SkinEditorSceneLibrary.cs | 20 +++---- .../Skinning/Editor/SkinSelectionHandler.cs | 4 +- 6 files changed, 59 insertions(+), 57 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index fc7e9e8ef1..c5e822a17a 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.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 osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -20,18 +18,18 @@ namespace osu.Game.Skinning.Editor { public partial class SkinBlueprint : SelectionBlueprint { - private Container box; + private Container box = null!; - private Container outlineBox; + private Container outlineBox = null!; - private AnchorOriginVisualiser anchorOriginVisualiser; + private AnchorOriginVisualiser anchorOriginVisualiser = null!; private Drawable drawable => (Drawable)Item; protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; public SkinBlueprint(ISkinnableDrawable component) : base(component) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 9812406aad..2998136aca 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.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.Collections.Specialized; using System.Diagnostics; @@ -28,7 +26,7 @@ namespace osu.Game.Skinning.Editor private readonly List> targetComponents = new List>(); [Resolved] - private SkinEditor editor { get; set; } + private SkinEditor editor { get; set; } = null!; public SkinBlueprintContainer(Drawable target) { @@ -61,7 +59,7 @@ namespace osu.Game.Skinning.Editor } } - private void componentsChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => + private void componentsChanged(object? sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) { diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 98b4e960dd..5c0c8b2427 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.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. -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -42,39 +41,39 @@ namespace osu.Game.Skinning.Editor protected override bool StartHidden => true; - private Drawable targetScreen; + private Drawable targetScreen = null!; - private OsuTextFlowContainer headerText; + private OsuTextFlowContainer headerText = null!; - private Bindable currentSkin; - - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } + private Bindable currentSkin = null!; [Resolved] - private SkinManager skins { get; set; } + private OsuGame? game { get; set; } [Resolved] - private OsuColour colours { get; set; } + private SkinManager skins { get; set; } = null!; [Resolved] - private RealmAccess realm { get; set; } + private OsuColour colours { get; set; } = null!; - [Resolved(canBeNull: true)] - private SkinEditorOverlay skinEditorOverlay { get; set; } + [Resolved] + private RealmAccess realm { get; set; } = null!; + + [Resolved] + private SkinEditorOverlay? skinEditorOverlay { get; set; } [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); private bool hasBegunMutating; - private Container content; + private Container? content; - private EditorSidebar componentsSidebar; - private EditorSidebar settingsSidebar; + private EditorSidebar componentsSidebar = null!; + private EditorSidebar settingsSidebar = null!; - [Resolved(canBeNull: true)] - private OnScreenDisplay onScreenDisplay { get; set; } + [Resolved] + private OnScreenDisplay? onScreenDisplay { get; set; } public SkinEditor() { @@ -234,11 +233,14 @@ namespace osu.Game.Skinning.Editor // Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target. content?.Clear(); + Scheduler.AddOnce(loadBlueprintContainer); Scheduler.AddOnce(populateSettings); void loadBlueprintContainer() { + Debug.Assert(content != null); + content.Child = new SkinBlueprintContainer(targetScreen); componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable) @@ -311,9 +313,9 @@ namespace osu.Game.Skinning.Editor private IEnumerable availableTargets => targetScreen.ChildrenOfType(); - private ISkinnableTarget getFirstTarget() => availableTargets.FirstOrDefault(); + private ISkinnableTarget? getFirstTarget() => availableTargets.FirstOrDefault(); - private ISkinnableTarget getTarget(GlobalSkinComponentLookup.LookupType target) + private ISkinnableTarget? getTarget(GlobalSkinComponentLookup.LookupType target) { return availableTargets.FirstOrDefault(c => c.Target == target); } @@ -327,7 +329,7 @@ namespace osu.Game.Skinning.Editor currentSkin.Value.ResetDrawableTarget(t); // add back default components - getTarget(t.Target).Reload(); + getTarget(t.Target)?.Reload(); } } @@ -342,7 +344,7 @@ namespace osu.Game.Skinning.Editor currentSkin.Value.UpdateDrawableTarget(t); skins.Save(skins.CurrentSkin.Value); - onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString())); + onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString() ?? "Unknown")); } protected override bool OnHover(HoverEvent e) => true; @@ -394,12 +396,18 @@ namespace osu.Game.Skinning.Editor // This is the best we can do for now. realm.Run(r => r.Refresh()); + var skinnableTarget = getFirstTarget(); + + // Import still should happen for now, even if not placeable (as it allows a user to import skin resources that would apply to legacy gameplay skins). + if (skinnableTarget == null) + return; + // place component var sprite = new SkinnableSprite { SpriteName = { Value = file.Name }, Origin = Anchor.Centre, - Position = getFirstTarget().ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position), + Position = skinnableTarget.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position), }; placeComponent(sprite, false); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 35e28ee665..f15afc7feb 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -1,10 +1,7 @@ // 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.Diagnostics; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -29,13 +26,12 @@ namespace osu.Game.Skinning.Editor protected override bool BlockNonPositionalInput => true; - [CanBeNull] - private SkinEditor skinEditor; + private SkinEditor? skinEditor; - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } + [Resolved] + private OsuGame game { get; set; } = null!; - private OsuScreen lastTargetScreen; + private OsuScreen? lastTargetScreen; private Vector2 lastDrawSize; @@ -81,6 +77,8 @@ namespace osu.Game.Skinning.Editor AddInternal(editor); + Debug.Assert(lastTargetScreen != null); + SetTarget(lastTargetScreen); }); } @@ -124,15 +122,15 @@ namespace osu.Game.Skinning.Editor { Scheduler.AddOnce(updateScreenSizing); - game?.Toolbar.Hide(); - game?.CloseAllOverlays(); + game.Toolbar.Hide(); + game.CloseAllOverlays(); } else { scalingContainer.SetCustomRect(null); if (lastTargetScreen?.HideOverlaysOnEnter != true) - game?.Toolbar.Show(); + game.Toolbar.Show(); } } @@ -158,7 +156,7 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(setTarget, screen); } - private void setTarget(OsuScreen target) + private void setTarget(OsuScreen? target) { if (target == null) return; diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs index 44045e8916..c2c7961aac 100644 --- a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -1,11 +1,8 @@ // 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -36,14 +33,14 @@ namespace osu.Game.Skinning.Editor private const float padding = 10; - [Resolved(canBeNull: true)] - private IPerformFromScreenRunner performer { get; set; } + [Resolved] + private IPerformFromScreenRunner? performer { get; set; } [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; [Resolved] - private Bindable> mods { get; set; } + private Bindable> mods { get; set; } = null!; public SkinEditorSceneLibrary() { @@ -107,7 +104,12 @@ namespace osu.Game.Skinning.Editor var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); - if (!ModUtils.CheckCompatibleSet(mods.Value.Append(replayGeneratingMod), out var invalid)) + IReadOnlyList usableMods = mods.Value; + + if (replayGeneratingMod != null) + usableMods = usableMods.Append(replayGeneratingMod).ToArray(); + + if (!ModUtils.CheckCompatibleSet(usableMods, out var invalid)) mods.Value = mods.Value.Except(invalid).ToArray(); if (replayGeneratingMod != null) @@ -130,7 +132,7 @@ namespace osu.Game.Skinning.Editor } [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + private void load(OverlayColourProvider? overlayColourProvider, OsuColour colours) { BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; Content.CornerRadius = 5; diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2c3f6238ec..d67cbef481 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.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; @@ -23,7 +21,7 @@ namespace osu.Game.Skinning.Editor public partial class SkinSelectionHandler : SelectionHandler { [Resolved] - private SkinEditor skinEditor { get; set; } + private SkinEditor skinEditor { get; set; } = null!; public override bool HandleRotation(float angle) { From 4352c56c3e4d8ea884d3872069be93a3795a443f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 17:52:49 +0900 Subject: [PATCH 099/162] Apply NRT to all simple auxiliary skin classes --- osu.Game/Skinning/Components/BigBlackBox.cs | 2 -- osu.Game/Skinning/LegacyRollingCounter.cs | 2 -- osu.Game/Skinning/LegacyScoreCounter.cs | 2 -- osu.Game/Skinning/LegacySkinDecoder.cs | 2 -- osu.Game/Skinning/LegacySpriteText.cs | 8 +++----- osu.Game/Skinning/SkinCustomColourLookup.cs | 2 -- osu.Game/Skinning/SkinnableSound.cs | 14 +++++--------- 7 files changed, 8 insertions(+), 24 deletions(-) diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs index 4210a70b72..043a276e49 100644 --- a/osu.Game/Skinning/Components/BigBlackBox.cs +++ b/osu.Game/Skinning/Components/BigBlackBox.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 JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game/Skinning/LegacyRollingCounter.cs b/osu.Game/Skinning/LegacyRollingCounter.cs index 465f1c3a20..8282b2f88b 100644 --- a/osu.Game/Skinning/LegacyRollingCounter.cs +++ b/osu.Game/Skinning/LegacyRollingCounter.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 osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 6a14ed93e9..88e7bbc23a 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.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.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 11c21d432f..1270f69339 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.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.Globalization; using osu.Game.Beatmaps.Formats; diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index b89f85778b..d6af52855b 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.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.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -16,7 +14,7 @@ namespace osu.Game.Skinning { private readonly LegacyFont font; - private LegacyGlyphStore glyphStore; + private LegacyGlyphStore glyphStore = null!; protected override char FixedWidthReferenceCharacter => '5'; @@ -49,7 +47,7 @@ namespace osu.Game.Skinning this.skin = skin; } - public ITexturedCharacterGlyph Get(string fontName, char character) + public ITexturedCharacterGlyph? Get(string fontName, char character) { string lookup = getLookupName(character); @@ -79,7 +77,7 @@ namespace osu.Game.Skinning } } - public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); } } } diff --git a/osu.Game/Skinning/SkinCustomColourLookup.cs b/osu.Game/Skinning/SkinCustomColourLookup.cs index 319b071bf5..b8e5ac9b53 100644 --- a/osu.Game/Skinning/SkinCustomColourLookup.cs +++ b/osu.Game/Skinning/SkinCustomColourLookup.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 - namespace osu.Game.Skinning { public class SkinCustomColourLookup diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index aeced9b517..ec296d4e89 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.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.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -39,13 +36,12 @@ namespace osu.Game.Skinning /// /// All raw s contained in this . /// - [NotNull, ItemNotNull] protected IEnumerable DrawableSamples => samplesContainer.Select(c => c.Sample).Where(s => s != null); private readonly AudioContainer samplesContainer; - [Resolved(CanBeNull = true)] - private IPooledSampleProvider samplePool { get; set; } + [Resolved] + private IPooledSampleProvider? samplePool { get; set; } /// /// Creates a new . @@ -59,7 +55,7 @@ namespace osu.Game.Skinning /// Creates a new with some initial samples. /// /// The initial samples. - public SkinnableSound([NotNull] IEnumerable samples) + public SkinnableSound(IEnumerable samples) : this() { this.samples = samples.ToArray(); @@ -69,7 +65,7 @@ namespace osu.Game.Skinning /// Creates a new with an initial sample. /// /// The initial sample. - public SkinnableSound([NotNull] ISampleInfo sample) + public SkinnableSound(ISampleInfo sample) : this(new[] { sample }) { } @@ -79,7 +75,7 @@ namespace osu.Game.Skinning /// /// The samples that should be played. /// - public ISampleInfo[] Samples + public ISampleInfo[]? Samples { get => samples; set From 758b4c8cfcc7b1ecbb91e64ea8230d211bd6de8f Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Thu, 26 Jan 2023 10:01:33 -0300 Subject: [PATCH 100/162] Do the thing aka fix the if --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 4 ++-- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 12 ++++++++---- osu.Game/Skinning/LegacySkin.cs | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 2f3a308b57..9b1ff9b22f 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning private readonly ParticleExplosion? particles; - public LegacyJudgementPieceNew(HitResult result, decimal? version, Func createMainDrawable, Texture? particleTexture) + public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Texture? particleTexture) { this.result = result; @@ -53,7 +53,7 @@ namespace osu.Game.Skinning if (result != HitResult.Miss) { //new judgement shows old as a temporary effect - AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, version, createMainDrawable, 1.05f, true) + AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f, true) { Blending = BlendingParameters.Additive, Anchor = Anchor.Centre, diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index ed3a88219e..69d38b06c5 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -17,15 +18,16 @@ namespace osu.Game.Skinning public partial class LegacyJudgementPieceOld : CompositeDrawable, IAnimatableJudgement { private readonly HitResult result; - private readonly decimal? version; private readonly float finalScale; private readonly bool forceTransforms; - public LegacyJudgementPieceOld(HitResult result, decimal? version, Func createMainDrawable, float finalScale = 1f, bool forceTransforms = false) + [Resolved] + private ISkinSource skin { get; set; } = null!; + + public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f, bool forceTransforms = false) { this.result = result; - this.version = version; this.finalScale = finalScale; this.forceTransforms = forceTransforms; @@ -58,7 +60,9 @@ namespace osu.Game.Skinning this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - if (version > 1) + decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; + + if (legacyVersion > 1) { this.MoveTo(new Vector2(0, -5)); this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 04bb551668..5f12d2ce23 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -390,9 +390,9 @@ namespace osu.Game.Skinning var particle = getParticleTexture(resultComponent.Component); if (particle != null) - return new LegacyJudgementPieceNew(resultComponent.Component, Configuration.LegacyVersion, createDrawable, particle); + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, particle); - return new LegacyJudgementPieceOld(resultComponent.Component, Configuration.LegacyVersion, createDrawable); + return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); } return null; From 2b55e05b10857a09c3162fec5ef965da3034e775 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Jan 2023 13:31:21 +0900 Subject: [PATCH 101/162] Adjust argon hit lighting further --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 2a53122cc1..9e86e1e411 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -151,10 +151,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon const float shrink_size = 0.8f; - // When the user has hit lighting disabled, we won't be showing the bright white flash. - // To make things look good, the surrounding animations are also slightly adjusted. - bool showFlash = configHitLighting.Value; - // Animating with the number present is distracting. // The number disappearing is hidden by the bright flash. number.FadeOut(flash_in_duration / 2); @@ -186,25 +182,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { outerGradient.ResizeTo(OUTER_GRADIENT_SIZE * shrink_size, resize_duration, Easing.OutElasticHalf); - if (showFlash) - { - outerGradient - .FadeColour(Color4.White, 80) - .Then() - .FadeOut(flash_in_duration); - } - else - { - outerGradient - .FadeColour(Color4.White, flash_in_duration * 8) - .FadeOut(flash_in_duration * 2); - } + outerGradient + .FadeColour(Color4.White, 80) + .Then() + .FadeOut(flash_in_duration); } - if (showFlash) + if (configHitLighting.Value) + { + flash.HitLighting = true; flash.FadeTo(1, flash_in_duration, Easing.OutQuint); - this.FadeOut(showFlash ? fade_out_time : fade_out_time / 2, Easing.OutQuad); + this.FadeOut(fade_out_time, Easing.OutQuad); + } + else + { + flash.HitLighting = false; + flash.FadeTo(1, flash_in_duration, Easing.OutQuint) + .Then() + .FadeOut(flash_in_duration, Easing.OutQuint); + + this.FadeOut(fade_out_time * 0.8f, Easing.OutQuad); + } break; } @@ -236,6 +235,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Child.AlwaysPresent = true; } + public bool HitLighting { get; set; } + protected override void Update() { base.Update(); @@ -244,7 +245,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { Type = EdgeEffectType.Glow, Colour = Colour, - Radius = OsuHitObject.OBJECT_RADIUS * 1.2f, + Radius = OsuHitObject.OBJECT_RADIUS * (HitLighting ? 1.2f : 0.6f), }; } } From 8a9a6c733c839fdff3dd24d114c6494b7a335c47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Jan 2023 13:55:34 +0900 Subject: [PATCH 102/162] Apply missed cleanup to `BackgroundDependencyLoader` call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs index c2c7961aac..0936be3ee8 100644 --- a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -131,7 +131,7 @@ namespace osu.Game.Skinning.Editor Height = BUTTON_HEIGHT; } - [BackgroundDependencyLoader(true)] + [BackgroundDependencyLoader] private void load(OverlayColourProvider? overlayColourProvider, OsuColour colours) { BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; From 74ab036f3e695e640f531d8a86ba885c89c24f69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Jan 2023 19:32:30 +0900 Subject: [PATCH 103/162] Refactor `SkinnableSound.Samples` to be non-nullable --- .../Objects/Drawables/DrawableHoldNote.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 +-- osu.Game/Skinning/SkinnableSound.cs | 6 +++--- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 759d2346e8..264a1bd5ec 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -376,7 +376,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void OnFree() { - slidingSample.Samples = null; + slidingSample.ClearSamples(); base.OnFree(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 4601af45d8..a7b02596d5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables PathVersion.UnbindFrom(HitObject.Path.Version); - slidingSample.Samples = null; + slidingSample?.ClearSamples(); } protected override void LoadSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index eed5c3e2e3..a5193f1b6e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.OnFree(); - spinningSample.Samples = null; + spinningSample.ClearSamples(); } protected override void LoadSamples() diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index be5a7f71e7..39f0888882 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -303,8 +303,7 @@ namespace osu.Game.Rulesets.Objects.Drawables samplesBindable.CollectionChanged -= onSamplesChanged; // Release the samples for other hitobjects to use. - if (Samples != null) - Samples.Samples = null; + Samples?.ClearSamples(); foreach (var obj in nestedHitObjects) { diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index ec296d4e89..475b79053a 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -75,13 +75,11 @@ namespace osu.Game.Skinning /// /// The samples that should be played. /// - public ISampleInfo[]? Samples + public ISampleInfo[] Samples { get => samples; set { - value ??= Array.Empty(); - if (samples == value) return; @@ -92,6 +90,8 @@ namespace osu.Game.Skinning } } + public void ClearSamples() => Samples = Array.Empty(); + private bool looping; /// From a126c72a4fbf150c59fba9e65bafe77ff02c5ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 15:17:35 +0100 Subject: [PATCH 104/162] Adjust skin resources test to facilitate loading many --- .../Skins/TestSceneSkinResources.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 748668b6ca..b29211f442 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -31,17 +31,14 @@ namespace osu.Game.Tests.Skins [Resolved] private SkinManager skins { get; set; } = null!; - private ISkin skin = null!; - - [BackgroundDependencyLoader] - private void load() - { - var imported = skins.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-skin.osk"), "ogg-skin.osk")).GetResultSafely(); - skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); - } - [Test] - public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo("sample")) != null); + public void TestRetrieveOggSample() + { + ISkin skin = null!; + + AddStep("import skin", () => skin = importSkinFromArchives(@"ogg-skin.osk")); + AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"sample")) != null); + } [Test] public void TestSampleRetrievalOrder() @@ -78,6 +75,12 @@ namespace osu.Game.Tests.Skins }); } + private Skin importSkinFromArchives(string filename) + { + var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); + return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); + } + private class TestSkin : Skin { public const string SAMPLE_NAME = "test-sample"; From 9bdb78791f0bab12c70c7f3ef6159a6720b33338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 15:23:15 +0100 Subject: [PATCH 105/162] Add failing test case --- .../Archives/conflicting-filenames-skin.osk | Bin 0 -> 3665 bytes osu.Game.Tests/Skins/TestSceneSkinResources.cs | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk diff --git a/osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk b/osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk new file mode 100644 index 0000000000000000000000000000000000000000..73576f3e22dd29c4c6a99ca62cf5e842f1760f33 GIT binary patch literal 3665 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 Q^%a4Fbl;F-BK_6tU*i>nr2qf` literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index b29211f442..aaec319b57 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -40,6 +40,16 @@ namespace osu.Game.Tests.Skins AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"sample")) != null); } + [Test] + public void TestRetrievalWithConflictingFilenames() + { + ISkin skin = null!; + + AddStep("import skin", () => skin = importSkinFromArchives(@"conflicting-filenames-skin.osk")); + AddAssert("texture is non-null", () => skin.GetTexture(@"spinner-osu") != null); + AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"spinner-osu")) != null); + } + [Test] public void TestSampleRetrievalOrder() { From 845bbf55fe5d48293afb159c603a177d00bffb65 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 Jan 2023 21:22:06 -0800 Subject: [PATCH 106/162] Add failing beatmap listing search on initial open test --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 7bde2e747d..0d081e8138 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -563,6 +563,18 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("featured artist filter is off", () => !getBeatmapListingOverlay().ChildrenOfType().First().Current.Contains(SearchGeneral.FeaturedArtists)); } + [Test] + public void TestBeatmapListingLinkSearchOnInitialOpen() + { + BeatmapListingOverlay getBeatmapListingOverlay() => Game.ChildrenOfType().FirstOrDefault(); + + AddStep("open beatmap overlay with test query", () => Game.SearchBeatmapSet("test")); + + AddUntilStep("wait for beatmap overlay to load", () => getBeatmapListingOverlay()?.State.Value == Visibility.Visible); + + AddAssert("beatmap overlay sorted by relevance", () => getBeatmapListingOverlay().ChildrenOfType().Single().Current.Value == SortCriteria.Relevance); + } + [Test] public void TestMainOverlaysClosesNotificationOverlay() { From 610d2f9dc7346ba8d634670f39bb2ffeeffcd037 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 Jan 2023 21:30:51 -0800 Subject: [PATCH 107/162] Fix beatmap listing potentially not sorting by relevance when searching via metadata --- .../Overlays/BeatmapListing/BeatmapListingSortTabControl.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs index 34e0408db6..0c2637ded3 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs @@ -3,6 +3,7 @@ #nullable disable +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -20,9 +21,9 @@ namespace osu.Game.Overlays.BeatmapListing private SearchCategory? lastCategory; private bool? lastHasQuery; - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); Reset(SearchCategory.Leaderboard, false); } From 20f4061e303774455802bfbae69313645e19fc8c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Jan 2023 00:24:05 +0300 Subject: [PATCH 108/162] Move ongoing operation wait step to proper place --- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 ++ .../Multiplayer/TestSceneAllPlayersQueueMode.cs | 15 --------------- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 13 ------------- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 0e9863a9f5..0f1ba9ba75 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -16,6 +16,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -85,6 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddUntilStep("wait for join", () => MultiplayerClient.RoomJoined); + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 6da9d37648..869b8bb328 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; @@ -44,14 +43,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] - /* - * TearDown : System.TimeoutException : "wait for ongoing operation to complete" timed out - * --TearDown - * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() - * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) - * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) - */ public void TestItemAddedToTheEndOfQueue() { addItem(() => OtherBeatmap); @@ -64,7 +55,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestNextItemSelectedAfterGameplayFinish() { addItem(() => OtherBeatmap); @@ -82,7 +72,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestItemsNotClearedWhenSwitchToHostOnlyMode() { addItem(() => OtherBeatmap); @@ -98,7 +87,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestCorrectItemSelectedAfterNewItemAdded() { addItem(() => OtherBeatmap); @@ -106,7 +94,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestCorrectRulesetSelectedAfterNewItemAdded() { addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo); @@ -124,7 +111,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestCorrectModsSelectedAfterNewItemAdded() { addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() }); @@ -153,7 +139,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null); AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded); - AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); if (ruleset != null) AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index dc36f539e3..78baa4a39b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -50,14 +50,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] - /* - * TearDown : System.TimeoutException : "wait for ongoing operation to complete" timed out - * --TearDown - * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() - * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) - * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) - */ public void TestItemStillSelectedAfterChangeToSameBeatmap() { selectNewItem(() => InitialBeatmap); @@ -66,7 +58,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestItemStillSelectedAfterChangeToOtherBeatmap() { selectNewItem(() => OtherBeatmap); @@ -75,7 +66,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestOnlyLastItemChangedAfterGameplayFinished() { RunGameplay(); @@ -90,7 +80,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestAddItemsAsHost() { addItem(() => OtherBeatmap); @@ -115,7 +104,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); BeatmapInfo otherBeatmap = null; - AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); @@ -131,7 +119,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); - AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } From 9e7f9c86cabfae4d82499df1d384e3d119f8d7f3 Mon Sep 17 00:00:00 2001 From: itsMapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Mon, 30 Jan 2023 11:14:29 -0600 Subject: [PATCH 109/162] flat hold tail --- .../Skinning/Argon/ArgonHoldNoteTailPiece.cs | 71 +------------------ 1 file changed, 2 insertions(+), 69 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs index b2cccfac26..74b4a7b661 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs @@ -1,84 +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 osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI.Scrolling; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { internal partial class ArgonHoldNoteTailPiece : CompositeDrawable { - private readonly IBindable direction = new Bindable(); - private readonly IBindable accentColour = new Bindable(); - - private readonly Container shadeContainer; - private readonly Circle hitLine; - public ArgonHoldNoteTailPiece() { - RelativeSizeAxes = Axes.X; + // holds end at the middle of the tail, + // so we do * 2 pull up the hold body to be the height of a note Height = ArgonNotePiece.NOTE_HEIGHT * 2; - - CornerRadius = ArgonNotePiece.CORNER_RADIUS; - Masking = true; - - InternalChildren = new Drawable[] - { - shadeContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - CornerRadius = ArgonNotePiece.CORNER_RADIUS, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.4f, - }, - }, - }, - hitLine = new Circle - { - RelativeSizeAxes = Axes.X, - Height = ArgonNotePiece.CORNER_RADIUS * 2, - }, - }; - } - - [BackgroundDependencyLoader(true)] - private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject) - { - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); - - if (drawableObject != null) - { - accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(onAccentChanged, true); - } - } - - private void onDirectionChanged(ValueChangedEvent direction) - { - hitLine.Anchor = hitLine.Origin = - shadeContainer.Anchor = shadeContainer.Origin = - direction.NewValue == ScrollingDirection.Up - ? Anchor.TopCentre - : Anchor.BottomCentre; - } - - private void onAccentChanged(ValueChangedEvent accent) - { - hitLine.Colour = accent.NewValue; } } } From 55a045b2b24443b4fef1e0e343dfe85d37ee91b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 15:59:50 +0100 Subject: [PATCH 110/162] Adjust beatmap skin resources test to facilitate loading many --- .../Skins/TestSceneBeatmapSkinResources.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 5256bcb3af..0bcaf217ab 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.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. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -20,29 +19,26 @@ namespace osu.Game.Tests.Skins public partial class TestSceneBeatmapSkinResources : OsuTestScene { [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; - private IWorkingBeatmap beatmap; - - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestRetrieveOggAudio() { - var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-beatmap.osz"), "ogg-beatmap.osz")).GetResultSafely(); + IWorkingBeatmap beatmap = null!; - imported?.PerformRead(s => + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"ogg-beatmap.osz")); + AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"sample")) != null); + AddAssert("track is non-null", () => { - beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); + using (var track = beatmap.LoadTrack()) + return track is not TrackVirtual; }); } - [Test] - public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); - - [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => + private IWorkingBeatmap importBeatmapFromArchives(string filename) { - using (var track = beatmap.LoadTrack()) - return track is not TrackVirtual; - }); + var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); + return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0])); + } } } From a8f828d203f8efab41d9ce23e2e34cc76825db3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 16:02:11 +0100 Subject: [PATCH 111/162] Add another failing test case --- .../Archives/conflicting-filenames-beatmap.osz | Bin 0 -> 6604 bytes .../Skins/TestSceneBeatmapSkinResources.cs | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz diff --git a/osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz b/osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz new file mode 100644 index 0000000000000000000000000000000000000000..c6b5f083ff6e5f17cb632ae21a29d2ebe58eb963 GIT binary patch literal 6604 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 0bcaf217ab..d9212386c3 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -35,6 +35,16 @@ namespace osu.Game.Tests.Skins }); } + [Test] + public void TestRetrievalWithConflictingFilenames() + { + IWorkingBeatmap beatmap = null!; + + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"conflicting-filenames-beatmap.osz")); + AddAssert("texture is non-null", () => beatmap.Skin.GetTexture(@"spinner-osu") != null); + AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"spinner-osu")) != null); + } + private IWorkingBeatmap importBeatmapFromArchives(string filename) { var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); From c5e1f54185b6ad3391f61b409035d06f79aaca46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 21:16:22 +0100 Subject: [PATCH 112/162] Fix sample store creation mutating shared resource store --- .../Rulesets/TestSceneDrawableRulesetDependencies.cs | 2 ++ osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 2 ++ osu.Game/Skinning/Skin.cs | 9 ++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 3fba21050e..5cfcca303f 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -150,6 +150,8 @@ namespace osu.Game.Tests.Rulesets public IBindable AggregateTempo => throw new NotImplementedException(); public int PlaybackConcurrency { get; set; } + + public void AddExtension(string extension) => throw new NotImplementedException(); } private class TestShaderManager : ShaderManager diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index c50f63c3b2..96b02ee4dc 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -157,6 +157,8 @@ namespace osu.Game.Rulesets.UI set => throw new NotSupportedException(); } + public void AddExtension(string extension) => throw new NotSupportedException(); + public void Dispose() { if (primary.IsNotNull()) primary.Dispose(); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 419dacadfd..37e98946c9 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -69,12 +69,15 @@ namespace osu.Game.Skinning storage ??= realmBackedStorage = new RealmBackedResourceStore(SkinInfo, resources.Files, resources.RealmAccess); var samples = resources.AudioManager?.GetSampleStore(storage); + if (samples != null) + { samples.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; - // osu-stable performs audio lookups in order of wav -> mp3 -> ogg. - // The GetSampleStore() call above internally adds wav and mp3, so ogg is added at the end to ensure expected ordering. - (storage as ResourceStore)?.AddExtension("ogg"); + // osu-stable performs audio lookups in order of wav -> mp3 -> ogg. + // The GetSampleStore() call above internally adds wav and mp3, so ogg is added at the end to ensure expected ordering. + samples.AddExtension(@"ogg"); + } Samples = samples; Textures = new TextureStore(resources.Renderer, new MaxDimensionLimitedTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); From b1cbe20cd82e93533b73ccfeeac17986129824a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 21:16:37 +0100 Subject: [PATCH 113/162] Adjust markdown text flow to framework-side changes --- .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 5f5b9acf56..dbc358882c 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -42,8 +42,12 @@ namespace osu.Game.Graphics.Containers.Markdown protected override void AddFootnoteBacklink(FootnoteLink footnoteBacklink) => AddDrawable(new OsuMarkdownFootnoteBacklink(footnoteBacklink)); - protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) - => CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic)); + protected override void ApplyEmphasisedCreationParameters(SpriteText spriteText, bool bold, bool italic) + { + base.ApplyEmphasisedCreationParameters(spriteText, bold, italic); + + spriteText.Font = spriteText.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic); + } protected override void AddCustomComponent(CustomContainerInline inline) { From 54d5d4e7c6a96fb4cd1d48d8b89ad6d508e181b0 Mon Sep 17 00:00:00 2001 From: Cootz Date: Tue, 31 Jan 2023 07:06:26 +0300 Subject: [PATCH 114/162] Fix for the issue --- .../Settings/Sections/Maintenance/MigrationSelectScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 80bf292057..57eb5ce60e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -47,6 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance var directoryInfos = target.GetDirectories(); var fileInfos = target.GetFiles(); + //With an empty disk (mb flash drive), this if could be false if (directoryInfos.Length > 0 || fileInfos.Length > 0) { // Quick test for whether there's already an osu! install at the target path. @@ -65,7 +66,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance return; } - target = target.CreateSubdirectory("osu-lazer"); + //Check for a root directory + if (target.Parent == null) + target = Directory.CreateDirectory(Path.Combine(target.FullName, "osu-lazer")); + else + target = target.CreateSubdirectory("osu-lazer"); } } catch (Exception e) From b18652b25fa366ea9191db55af53b43fc02fcb30 Mon Sep 17 00:00:00 2001 From: Cootz Date: Tue, 31 Jan 2023 10:14:21 +0300 Subject: [PATCH 115/162] CreateSubDirectory removed. Fixes the empty root issue --- .../Sections/Maintenance/MigrationSelectScreen.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 57eb5ce60e..159a99809d 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -47,8 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance var directoryInfos = target.GetDirectories(); var fileInfos = target.GetFiles(); - //With an empty disk (mb flash drive), this if could be false - if (directoryInfos.Length > 0 || fileInfos.Length > 0) + if (directoryInfos.Length > 0 || fileInfos.Length > 0 || target.Parent == null) { // Quick test for whether there's already an osu! install at the target path. if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME)) @@ -66,11 +65,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance return; } - //Check for a root directory - if (target.Parent == null) - target = Directory.CreateDirectory(Path.Combine(target.FullName, "osu-lazer")); - else - target = target.CreateSubdirectory("osu-lazer"); + //We aren't using CreateSubDirectory due to the flaw with a root of a drive + target = Directory.CreateDirectory(Path.Combine(target.FullName, "osu-lazer")); } } catch (Exception e) From 3d8b35184fe2edf3dd8a4d0a8ad52a9b6f833999 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Jan 2023 16:24:06 +0900 Subject: [PATCH 116/162] 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 71944065bf..4bdf5d68c5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - +