diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 17f365f820..a8a65f7edb 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -17,6 +18,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Testing; using osu.Framework.Testing.Input; using osu.Game.Audio; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -103,6 +105,23 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("contract", () => this.ChildrenOfType().Single().NewPartScale = Vector2.One); } + [Test] + public void TestRotation() + { + createTest(() => + { + var skinContainer = new LegacySkinContainer(renderer, provideMiddle: true, enableRotation: true); + var legacyCursorTrail = new LegacyRotatingCursorTrail(skinContainer) + { + NewPartScale = new Vector2(10) + }; + + skinContainer.Child = legacyCursorTrail; + + return skinContainer; + }); + } + private void createTest(Func createContent) => AddStep("create trail", () => { Clear(); @@ -121,12 +140,14 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly IRenderer renderer; private readonly bool provideMiddle; private readonly bool provideCursor; + private readonly bool enableRotation; - public LegacySkinContainer(IRenderer renderer, bool provideMiddle, bool provideCursor = true) + public LegacySkinContainer(IRenderer renderer, bool provideMiddle, bool provideCursor = true, bool enableRotation = false) { this.renderer = renderer; this.provideMiddle = provideMiddle; this.provideCursor = provideCursor; + this.enableRotation = enableRotation; RelativeSizeAxes = Axes.Both; } @@ -152,7 +173,19 @@ namespace osu.Game.Rulesets.Osu.Tests public ISample GetSample(ISampleInfo sampleInfo) => null; - public IBindable GetConfig(TLookup lookup) => null; + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case OsuSkinConfiguration osuLookup: + if (osuLookup == OsuSkinConfiguration.CursorTrailRotate) + return SkinUtils.As(new BindableBool(enableRotation)); + + break; + } + + return null; + } public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; @@ -185,5 +218,19 @@ namespace osu.Game.Rulesets.Osu.Tests MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos)); } } + + private partial class LegacyRotatingCursorTrail : LegacyCursorTrail + { + public LegacyRotatingCursorTrail([NotNull] ISkin skin) + : base(skin) + { + } + + protected override void Update() + { + base.Update(); + PartRotation += (float)(Time.Elapsed * 0.1); + } + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs index 375d81049d..e526c4f14c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public partial class LegacyCursor : SkinnableCursor { + public static readonly int REVOLUTION_DURATION = 10000; + private const float pressed_scale = 1.3f; private const float released_scale = 1f; @@ -52,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void LoadComplete() { if (spin) - ExpandTarget.Spin(10000, RotationDirection.Clockwise); + ExpandTarget.Spin(REVOLUTION_DURATION, RotationDirection.Clockwise); } public override void Expand() diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs index ca0002d8c0..375bef721d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs @@ -34,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void load(OsuConfigManager config, ISkinSource skinSource) { cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); + AllowPartRotation = skin.GetConfig(OsuSkinConfiguration.CursorTrailRotate)?.Value ?? true; Texture = skin.GetTexture("cursortrail"); diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 9685ab685d..81488ca1a3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Skinning CursorCentre, CursorExpand, CursorRotate, + CursorTrailRotate, HitCircleOverlayAboveNumber, // ReSharper disable once IdentifierTypo diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 5132dc2859..1c2d69fa00 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -34,19 +34,24 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor /// protected virtual float FadeExponent => 1.7f; - private readonly TrailPart[] parts = new TrailPart[max_sprites]; - private int currentIndex; - private IShader shader; - private double timeOffset; - private float time; - /// /// The scale used on creation of a new trail part. /// - public Vector2 NewPartScale = Vector2.One; + public Vector2 NewPartScale { get; set; } = Vector2.One; - private Anchor trailOrigin = Anchor.Centre; + /// + /// The rotation (in degrees) to apply to trail parts when is true. + /// + public float PartRotation { get; set; } + /// + /// Whether to rotate trail parts based on the value of . + /// + protected bool AllowPartRotation { get; set; } + + /// + /// The trail part texture origin. + /// protected Anchor TrailOrigin { get => trailOrigin; @@ -57,6 +62,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } + private readonly TrailPart[] parts = new TrailPart[max_sprites]; + private Anchor trailOrigin = Anchor.Centre; + private int currentIndex; + private IShader shader; + private double timeOffset; + private float time; + public CursorTrail() { // as we are currently very dependent on having a running clock, let's make our own clock for the time being. @@ -220,6 +232,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private float time; private float fadeExponent; + private float angle; private readonly TrailPart[] parts = new TrailPart[max_sprites]; private Vector2 originPosition; @@ -239,6 +252,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor texture = Source.texture; time = Source.time; fadeExponent = Source.FadeExponent; + angle = Source.AllowPartRotation ? float.DegreesToRadians(Source.PartRotation) : 0; originPosition = Vector2.Zero; @@ -279,6 +293,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor renderer.PushLocalMatrix(DrawInfo.Matrix); + float sin = MathF.Sin(angle); + float cos = MathF.Cos(angle); + foreach (var part in parts) { if (part.InvalidationID == -1) @@ -289,7 +306,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), + Position = rotateAround( + new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), + part.Position, sin, cos), TexturePosition = textureRect.BottomLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, @@ -298,7 +317,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), + Position = rotateAround( + new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, + part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), part.Position, sin, cos), TexturePosition = textureRect.BottomRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, @@ -307,7 +328,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), + Position = rotateAround( + new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), + part.Position, sin, cos), TexturePosition = textureRect.TopRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, @@ -316,7 +339,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), + Position = rotateAround( + new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), + part.Position, sin, cos), TexturePosition = textureRect.TopLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, @@ -330,6 +355,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor shader.Unbind(); } + private static Vector2 rotateAround(Vector2 input, Vector2 origin, float sin, float cos) + { + float xTranslated = input.X - origin.X; + float yTranslated = input.Y - origin.Y; + + return new Vector2(xTranslated * cos - yTranslated * sin, xTranslated * sin + yTranslated * cos) + origin; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index c2f7d84f5e..e84fb9e2d6 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -36,6 +36,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor /// public Vector2 CurrentExpandedScale => skinnableCursor.ExpandTarget?.Scale ?? Vector2.One; + /// + /// The current rotation of the cursor. + /// + public float CurrentRotation => skinnableCursor.ExpandTarget?.Rotation ?? 0; + public IBindable CursorScale => cursorScale; /// diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 8c0871d54f..974d99d7c8 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -83,7 +83,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor base.Update(); if (cursorTrail.Drawable is CursorTrail trail) + { trail.NewPartScale = ActiveCursor.CurrentExpandedScale; + trail.PartRotation = ActiveCursor.CurrentRotation; + } } public bool OnPressed(KeyBindingPressEvent e)