diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 892c89ebc2..0013f23057 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -100,7 +100,10 @@ namespace osu.Game.Rulesets.Osu.Edit () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), new RadioButton("Triangle", () => GridType.Value = PositionSnapGridType.Triangle, - () => new Triangle()) + () => new Triangle()), + new RadioButton("Circle", + () => GridType.Value = PositionSnapGridType.Circle, + () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), } }, }; @@ -177,5 +180,6 @@ namespace osu.Game.Rulesets.Osu.Edit { Square, Triangle, + Circle, } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index b0ac8467c1..1e7d07dbdd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -135,6 +135,14 @@ namespace osu.Game.Rulesets.Osu.Edit positionSnapGrid = triangularPositionSnapGrid; break; + case PositionSnapGridType.Circle: + var circularPositionSnapGrid = new CircularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => circularPositionSnapGrid.Spacing = s.NewValue, true); + + positionSnapGrid = circularPositionSnapGrid; + break; + default: throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs new file mode 100644 index 0000000000..b9b0cbe389 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public partial class CircularPositionSnapGrid : PositionSnapGrid + { + private float spacing = 1; + + /// + /// The spacing between grid lines of this . + /// + public float Spacing + { + get => spacing; + set + { + if (spacing <= 0) + throw new ArgumentException("Grid spacing must be positive."); + + spacing = value; + GridCache.Invalidate(); + } + } + + protected override void CreateContent() + { + var drawSize = DrawSize; + + // Calculate the maximum distance from the origin to the edge of the grid. + float maxDist = MathF.Max( + MathF.Max(StartPosition.Length, (StartPosition - drawSize).Length), + MathF.Max((StartPosition - new Vector2(drawSize.X, 0)).Length, (StartPosition - new Vector2(0, drawSize.Y)).Length) + ); + + generateCircles((int)(maxDist / Spacing) + 1); + + GenerateOutline(drawSize); + } + + private void generateCircles(int count) + { + // Make lines the same width independent of display resolution. + float lineWidth = 2 * DrawWidth / ScreenSpaceDrawQuad.Width; + + List generatedCircles = new List(); + + for (int i = 0; i < count; i++) + { + // Add a minimum diameter so the center circle is clearly visible. + float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing * 2); + + var gridCircle = new CircularContainer + { + BorderColour = Colour4.White, + BorderThickness = lineWidth, + Alpha = 0.2f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = diameter, + Height = diameter, + Position = StartPosition, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0f, + } + }; + + generatedCircles.Add(gridCircle); + } + + if (generatedCircles.Count == 0) + return; + + generatedCircles.First().Alpha = 0.8f; + + AddRangeInternal(generatedCircles); + } + + public override Vector2 GetSnappedPosition(Vector2 original) + { + Vector2 relativeToStart = original - StartPosition; + + if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) + return StartPosition; + + float length = relativeToStart.Length; + float wantedLength = MathF.Round(length / Spacing) * Spacing; + + return StartPosition + Vector2.Multiply(relativeToStart, wantedLength / length); + } + } +}