From f3b88c318b57ee222973779359611fdc18fe6eb6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 17:49:14 +0100 Subject: [PATCH 01/33] Add rotation to snap grid visual --- .../TestSceneRectangularPositionSnapGrid.cs | 16 +- .../Components/RectangularPositionSnapGrid.cs | 140 +++++++++++++++--- 2 files changed, 125 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index e73a45e154..210af09055 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -33,28 +33,30 @@ namespace osu.Game.Tests.Visual.Editing }, content = new Container { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), } }); } private static readonly object[][] test_cases = { - new object[] { new Vector2(0, 0), new Vector2(10, 10) }, - new object[] { new Vector2(240, 180), new Vector2(10, 15) }, - new object[] { new Vector2(160, 120), new Vector2(30, 20) }, - new object[] { new Vector2(480, 360), new Vector2(100, 100) }, + new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f }, + new object[] { new Vector2(240, 180), new Vector2(10, 15), 30f }, + new object[] { new Vector2(160, 120), new Vector2(30, 20), -30f }, + new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f }, }; [TestCaseSource(nameof(test_cases))] - public void TestRectangularGrid(Vector2 position, Vector2 spacing) + public void TestRectangularGrid(Vector2 position, Vector2 spacing, float rotation) { RectangularPositionSnapGrid grid = null; AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position) { RelativeSizeAxes = Axes.Both, - Spacing = spacing + Spacing = spacing, + GridLineRotation = rotation }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index cfc01fe17b..160e7e026b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -15,10 +15,20 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class RectangularPositionSnapGrid : CompositeDrawable { + private Vector2 startPosition; + /// /// The position of the origin of this in local coordinates. /// - public Vector2 StartPosition { get; } + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + gridCache.Invalidate(); + } + } private Vector2 spacing = Vector2.One; @@ -38,11 +48,27 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private float gridLineRotation; + + /// + /// The rotation in degrees of the grid lines of this . + /// + public float GridLineRotation + { + get => gridLineRotation; + set + { + gridLineRotation = value; + gridCache.Invalidate(); + } + } + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); public RectangularPositionSnapGrid(Vector2 startPosition) { StartPosition = startPosition; + Masking = true; AddLayout(gridCache); } @@ -65,47 +91,43 @@ namespace osu.Game.Screens.Edit.Compose.Components private void createContent() { var drawSize = DrawSize; + var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); - generateGridLines(Direction.Horizontal, StartPosition.Y, 0, -Spacing.Y); - generateGridLines(Direction.Horizontal, StartPosition.Y, drawSize.Y, Spacing.Y); + generateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), GridLineRotation + 90, drawSize); + generateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), GridLineRotation + 90, drawSize); - generateGridLines(Direction.Vertical, StartPosition.X, 0, -Spacing.X); - generateGridLines(Direction.Vertical, StartPosition.X, drawSize.X, Spacing.X); + generateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), GridLineRotation, drawSize); + generateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), GridLineRotation, drawSize); + + generateOutline(drawSize); } - private void generateGridLines(Direction direction, float startPosition, float endPosition, float step) + private void generateGridLines(Vector2 step, float rotation, Vector2 drawSize) { int index = 0; - float currentPosition = startPosition; + var currentPosition = startPosition; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; List generatedLines = new List(); - while (Precision.AlmostBigger((endPosition - currentPosition) * Math.Sign(step), 0)) + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) { var gridLine = new Box { Colour = Colour4.White, Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = rotation, }; - if (direction == Direction.Horizontal) - { - gridLine.Origin = Anchor.CentreLeft; - gridLine.RelativeSizeAxes = Axes.X; - gridLine.Height = lineWidth; - gridLine.Y = currentPosition; - } - else - { - gridLine.Origin = Anchor.TopCentre; - gridLine.RelativeSizeAxes = Axes.Y; - gridLine.Width = lineWidth; - gridLine.X = currentPosition; - } - generatedLines.Add(gridLine); index += 1; @@ -116,11 +138,81 @@ namespace osu.Game.Screens.Edit.Compose.Components return; generatedLines.First().Alpha = 0.3f; - generatedLines.Last().Alpha = 0.3f; AddRangeInternal(generatedLines); } + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + private void generateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + public Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = original - StartPosition; From f2edd705ea537774a662ca6121092b137bb2bb8e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 18:56:49 +0100 Subject: [PATCH 02/33] add rotation to snapped position --- .../Components/RectangularPositionSnapGrid.cs | 5 +++-- osu.Game/Utils/GeometryUtils.cs | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 160e7e026b..ea9eaf41bb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; using osu.Framework.Utils; +using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -215,11 +216,11 @@ namespace osu.Game.Screens.Edit.Compose.Components public Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = original - StartPosition; + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 offset = Vector2.Divide(relativeToStart, Spacing); Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); - return StartPosition + Vector2.Multiply(roundedOffset, Spacing); + return StartPosition + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing), -GridLineRotation); } } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 725e93d098..fcc6b8ae2a 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -27,9 +27,8 @@ namespace osu.Game.Utils point.X -= origin.X; point.Y -= origin.Y; - Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); + Vector2 ret = RotateVector(point, angle); + Matrix2 ret.X += origin.X; ret.Y += origin.Y; @@ -37,6 +36,19 @@ namespace osu.Game.Utils return ret; } + /// + /// Rotate a vector around the origin. + /// + /// The vector. + /// The angle to rotate (in degrees). + public static Vector2 RotateVector(Vector2 vector, float angle) + { + return new Vector2( + vector.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + vector.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)), + vector.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + vector.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)) + ); + } + /// /// Given a flip direction, a surrounding quad for all selected objects, and a position, /// will return the flipped position in screen space coordinates. From 2193601f3a70150d2e777939b3e75c9a6dd248db Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 20:00:24 +0100 Subject: [PATCH 03/33] fix typo --- osu.Game/Utils/GeometryUtils.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index fcc6b8ae2a..e0d217dd48 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -28,7 +28,6 @@ namespace osu.Game.Utils point.Y -= origin.Y; Vector2 ret = RotateVector(point, angle); - Matrix2 ret.X += origin.X; ret.Y += origin.Y; From 92c3b142a4941b14c83c479079adec3d6d6a9be5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 20:52:11 +0100 Subject: [PATCH 04/33] Added Triangular snap grid --- .../TestSceneTriangularPositionSnapGrid.cs | 108 ++++++++ .../Components/TriangularPositionSnapGrid.cs | 258 ++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs new file mode 100644 index 0000000000..2f5ffd8423 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.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. + +#nullable disable + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneTriangularPositionSnapGrid : OsuManualInputManagerTestScene + { + private Container content; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Gray + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + } + }); + } + + private static readonly object[][] test_cases = + { + new object[] { new Vector2(0, 0), 10, 0f }, + new object[] { new Vector2(240, 180), 10, 10f }, + new object[] { new Vector2(160, 120), 30, -10f }, + new object[] { new Vector2(480, 360), 100, 0f }, + }; + + [TestCaseSource(nameof(test_cases))] + public void TestTriangularGrid(Vector2 position, float spacing, float rotation) + { + TriangularPositionSnapGrid grid = null; + + AddStep("create grid", () => Child = grid = new TriangularPositionSnapGrid(position) + { + RelativeSizeAxes = Axes.Both, + Spacing = spacing, + GridLineRotation = rotation + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + private partial class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs new file mode 100644 index 0000000000..58889bd085 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -0,0 +1,258 @@ +// 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.Layout; +using osu.Framework.Utils; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public partial class TriangularPositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + gridCache.Invalidate(); + } + } + + 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(); + } + } + + private float gridLineRotation; + + /// + /// The rotation in degrees of the grid lines of this . + /// + public float GridLineRotation + { + get => gridLineRotation; + set + { + gridLineRotation = value; + gridCache.Invalidate(); + } + } + + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + public TriangularPositionSnapGrid(Vector2 startPosition) + { + StartPosition = startPosition; + Masking = true; + + AddLayout(gridCache); + } + + protected override void Update() + { + base.Update(); + + if (!gridCache.IsValid) + { + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + createContent(); + + gridCache.Validate(); + } + } + + private const float sqrt3 = 1.73205080757f; + private const float sqrt3_over2 = 0.86602540378f; + private const float one_over_sqrt3 = 0.57735026919f; + + private void createContent() + { + var drawSize = DrawSize; + float stepSpacing = Spacing * sqrt3_over2; + var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 30); + var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); + var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); + + generateGridLines(step1, drawSize); + generateGridLines(-step1, drawSize); + + generateGridLines(step2, drawSize); + generateGridLines(-step2, drawSize); + + generateGridLines(step3, drawSize); + generateGridLines(-step3, drawSize); + + generateOutline(drawSize); + } + + private void generateGridLines(Vector2 step, Vector2 drawSize) + { + int index = 0; + var currentPosition = startPosition; + + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; + + List generatedLines = new List(); + + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) + { + var gridLine = new Box + { + Colour = Colour4.White, + Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + }; + + generatedLines.Add(gridLine); + + index += 1; + currentPosition = startPosition + index * step; + } + + if (generatedLines.Count == 0) + return; + + generatedLines.First().Alpha = 0.3f; + + AddRangeInternal(generatedLines); + } + + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + private void generateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public Vector2 GetSnappedPosition(Vector2 original) + { + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); + Vector2 hex = pixelToHex(relativeToStart); + + return StartPosition + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation); + } + + private Vector2 pixelToHex(Vector2 pixel) + { + float x = pixel.X / Spacing; + float y = pixel.Y / Spacing; + // Algorithm from Charles Chambers + // with modifications and comments by Chris Cox 2023 + // + float t = sqrt3 * y + 1; // scaled y, plus phase + float temp1 = MathF.Floor(t + x); // (y+x) diagonal, this calc needs floor + float temp2 = t - x; // (y-x) diagonal, no floor needed + float temp3 = 2 * x + 1; // scaled horizontal, no floor needed, needs +1 to get correct phase + float qf = (temp1 + temp3) / 3.0f; // pseudo x with fraction + float rf = (temp1 + temp2) / 3.0f; // pseudo y with fraction + float q = MathF.Floor(qf); // pseudo x, quantized and thus requires floor + float r = MathF.Floor(rf); // pseudo y, quantized and thus requires floor + return new Vector2(q, r); + } + + private Vector2 hexToPixel(Vector2 hex) + { + return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); + } + } +} From d0c8b285cefc29025407597467357cd2940cb862 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 21:00:47 +0100 Subject: [PATCH 05/33] clean up code duplication --- .../Components/LinedPositionSnapGrid.cs | 173 +++++++++++++++++ .../Components/RectangularPositionSnapGrid.cs | 167 +--------------- .../Components/TriangularPositionSnapGrid.cs | 179 ++---------------- 3 files changed, 195 insertions(+), 324 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs new file mode 100644 index 0000000000..642a125265 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -0,0 +1,173 @@ +// 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.Layout; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract partial class LinedPositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + GridCache.Invalidate(); + } + } + + protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + protected LinedPositionSnapGrid(Vector2 startPosition) + { + StartPosition = startPosition; + Masking = true; + + AddLayout(GridCache); + } + + protected override void Update() + { + base.Update(); + + if (!GridCache.IsValid) + { + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + CreateContent(); + + GridCache.Validate(); + } + } + + protected abstract void CreateContent(); + + protected void GenerateGridLines(Vector2 step, Vector2 drawSize) + { + int index = 0; + var currentPosition = startPosition; + + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; + + List generatedLines = new List(); + + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) + { + var gridLine = new Box + { + Colour = Colour4.White, + Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + }; + + generatedLines.Add(gridLine); + + index += 1; + currentPosition = startPosition + index * step; + } + + if (generatedLines.Count == 0) + return; + + generatedLines.First().Alpha = 0.3f; + + AddRangeInternal(generatedLines); + } + + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + protected void GenerateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public abstract Vector2 GetSnappedPosition(Vector2 original); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index ea9eaf41bb..14a0e3625a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,35 +2,15 @@ // 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.Layout; -using osu.Framework.Utils; using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public partial class RectangularPositionSnapGrid : CompositeDrawable + public partial class RectangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - gridCache.Invalidate(); - } - } - private Vector2 spacing = Vector2.One; /// @@ -67,154 +47,25 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); public RectangularPositionSnapGrid(Vector2 startPosition) + : base(startPosition) { - StartPosition = startPosition; - Masking = true; - - AddLayout(gridCache); } - protected override void Update() - { - base.Update(); - - if (!gridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - createContent(); - - gridCache.Validate(); - } - } - - private void createContent() + protected override void CreateContent() { var drawSize = DrawSize; var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); - generateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), GridLineRotation + 90, drawSize); - generateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), GridLineRotation + 90, drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), drawSize); - generateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), GridLineRotation, drawSize); - generateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), GridLineRotation, drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), drawSize); - generateOutline(drawSize); + GenerateOutline(drawSize); } - private void generateGridLines(Vector2 step, float rotation, Vector2 drawSize) - { - int index = 0; - var currentPosition = startPosition; - - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; - - List generatedLines = new List(); - - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) - { - var gridLine = new Box - { - Colour = Colour4.White, - Alpha = 0.1f, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.None, - Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = rotation, - }; - - generatedLines.Add(gridLine); - - index += 1; - currentPosition = startPosition + index * step; - } - - if (generatedLines.Count == 0) - return; - - generatedLines.First().Alpha = 0.3f; - - AddRangeInternal(generatedLines); - } - - private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) - { - return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || - (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; - } - - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) - { - var p2 = lineStart + lineDir; - - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); - - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); - - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); - - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); - } - - private void generateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public Vector2 GetSnappedPosition(Vector2 original) + public override Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 offset = Vector2.Divide(relativeToStart, Spacing); diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index 58889bd085..4b6c5dcfe4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -2,35 +2,13 @@ // 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.Layout; -using osu.Framework.Utils; using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public partial class TriangularPositionSnapGrid : CompositeDrawable + public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - gridCache.Invalidate(); - } - } - private float spacing = 1; /// @@ -45,7 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components throw new ArgumentException("Grid spacing must be positive."); spacing = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } @@ -60,40 +38,20 @@ namespace osu.Game.Screens.Edit.Compose.Components set { gridLineRotation = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } - private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - public TriangularPositionSnapGrid(Vector2 startPosition) + : base(startPosition) { - StartPosition = startPosition; - Masking = true; - - AddLayout(gridCache); - } - - protected override void Update() - { - base.Update(); - - if (!gridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - createContent(); - - gridCache.Validate(); - } } private const float sqrt3 = 1.73205080757f; private const float sqrt3_over2 = 0.86602540378f; private const float one_over_sqrt3 = 0.57735026919f; - private void createContent() + protected override void CreateContent() { var drawSize = DrawSize; float stepSpacing = Spacing * sqrt3_over2; @@ -101,130 +59,19 @@ namespace osu.Game.Screens.Edit.Compose.Components var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); - generateGridLines(step1, drawSize); - generateGridLines(-step1, drawSize); + GenerateGridLines(step1, drawSize); + GenerateGridLines(-step1, drawSize); - generateGridLines(step2, drawSize); - generateGridLines(-step2, drawSize); + GenerateGridLines(step2, drawSize); + GenerateGridLines(-step2, drawSize); - generateGridLines(step3, drawSize); - generateGridLines(-step3, drawSize); + GenerateGridLines(step3, drawSize); + GenerateGridLines(-step3, drawSize); - generateOutline(drawSize); + GenerateOutline(drawSize); } - private void generateGridLines(Vector2 step, Vector2 drawSize) - { - int index = 0; - var currentPosition = startPosition; - - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; - - List generatedLines = new List(); - - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) - { - var gridLine = new Box - { - Colour = Colour4.White, - Alpha = 0.1f, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.None, - Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), - }; - - generatedLines.Add(gridLine); - - index += 1; - currentPosition = startPosition + index * step; - } - - if (generatedLines.Count == 0) - return; - - generatedLines.First().Alpha = 0.3f; - - AddRangeInternal(generatedLines); - } - - private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) - { - return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || - (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; - } - - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) - { - var p2 = lineStart + lineDir; - - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); - - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); - - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); - - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); - } - - private void generateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public Vector2 GetSnappedPosition(Vector2 original) + public override Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 hex = pixelToHex(relativeToStart); From a20c430d6f1c4e8deeb2ca974d719ae032bd26fd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:35:00 +0100 Subject: [PATCH 06/33] fix wrong grid cache being used --- .../Compose/Components/RectangularPositionSnapGrid.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 14a0e3625a..930a592850 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; -using osu.Framework.Layout; using osu.Game.Utils; using osuTK; @@ -25,7 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components throw new ArgumentException("Grid spacing must be positive."); spacing = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } @@ -40,12 +38,10 @@ namespace osu.Game.Screens.Edit.Compose.Components set { gridLineRotation = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } - private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - public RectangularPositionSnapGrid(Vector2 startPosition) : base(startPosition) { From b16c232490a5353dbf3fd4e59390f8147b4e816b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:36:30 +0100 Subject: [PATCH 07/33] add basic control by grid tool box --- .../Edit/OsuHitObjectComposer.cs | 11 ++ osu.Game/Rulesets/Edit/GridToolboxGroup.cs | 108 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/GridToolboxGroup.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 448cfaf84c..e487a5d490 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Cached(typeof(IDistanceSnapProvider))] protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); + [Cached] + protected readonly GridToolboxGroup GridToolboxGroup = new GridToolboxGroup(); + [Cached] protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); @@ -99,8 +102,16 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); + GridToolboxGroup.StartPositionX.ValueChanged += x => + rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y); + GridToolboxGroup.StartPositionY.ValueChanged += y => + rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue); + GridToolboxGroup.Spacing.ValueChanged += s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue); + GridToolboxGroup.GridLinesRotation.ValueChanged += r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue; + RightToolbox.AddRange(new EditorToolboxGroup[] { + GridToolboxGroup, new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs b/osu.Game/Rulesets/Edit/GridToolboxGroup.cs new file mode 100644 index 0000000000..b6903c1369 --- /dev/null +++ b/osu.Game/Rulesets/Edit/GridToolboxGroup.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit; + +namespace osu.Game.Rulesets.Edit +{ + public partial class GridToolboxGroup : EditorToolboxGroup + { + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + + public GridToolboxGroup() + : base("grid") + { + } + + public BindableFloat StartPositionX { get; } = new BindableFloat(256f) + { + MinValue = 0f, + MaxValue = 512f, + Precision = 1f + }; + + public BindableFloat StartPositionY { get; } = new BindableFloat(192) + { + MinValue = 0f, + MaxValue = 384f, + Precision = 1f + }; + + public BindableFloat Spacing { get; } = new BindableFloat(4f) + { + MinValue = 4f, + MaxValue = 128f, + Precision = 1f + }; + + public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) + { + MinValue = -180f, + MaxValue = 180f, + Precision = 1f + }; + + private ExpandableSlider startPositionXSlider = null!; + private ExpandableSlider startPositionYSlider = null!; + private ExpandableSlider spacingSlider = null!; + private ExpandableSlider gridLinesRotationSlider = null!; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + startPositionXSlider = new ExpandableSlider + { + Current = StartPositionX + }, + startPositionYSlider = new ExpandableSlider + { + Current = StartPositionY + }, + spacingSlider = new ExpandableSlider + { + Current = Spacing + }, + gridLinesRotationSlider = new ExpandableSlider + { + Current = GridLinesRotation + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + StartPositionX.BindValueChanged(x => + { + startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; + startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}"; + }, true); + + StartPositionY.BindValueChanged(y => + { + startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}"; + startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}"; + }, true); + + Spacing.BindValueChanged(spacing => + { + spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; + spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; + }, true); + + GridLinesRotation.BindValueChanged(rotation => + { + gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:N0}"; + gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; + }, true); + } + } +} From 0ce1a48e68ea26d9295a611e6648ffafc8a8651f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:54:30 +0100 Subject: [PATCH 08/33] Add comment --- .../Edit/Compose/Components/TriangularPositionSnapGrid.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index 4b6c5dcfe4..af44641f5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -99,6 +99,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2 hexToPixel(Vector2 hex) { + // Taken from + // with modifications for the different definition of size. return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); } } From f223487e1cd9cd2a391f79575a13222cfa19987e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:10:06 +0100 Subject: [PATCH 09/33] improve code --- .../Editor/TestSceneOsuEditorGrids.cs | 7 +- .../Edit/OsuGridToolboxGroup.cs | 71 +++++++++++++++---- .../Edit/OsuHitObjectComposer.cs | 18 ++--- .../Edit/OsuRectangularPositionSnapGrid.cs | 69 ------------------ .../TestSceneRectangularPositionSnapGrid.cs | 3 +- .../Screens/Editors/LadderEditorScreen.cs | 2 +- .../Components/LinedPositionSnapGrid.cs | 3 +- .../Components/RectangularPositionSnapGrid.cs | 5 -- .../Components/TriangularPositionSnapGrid.cs | 5 -- 9 files changed, 75 insertions(+), 108 deletions(-) rename osu.Game/Rulesets/Edit/GridToolboxGroup.cs => osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs (65%) delete mode 100644 osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index d14e593587..299db23ccc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -7,6 +7,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; @@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); AddStep("move cursor to (1, 1)", () => { - var composer = Editor.ChildrenOfType().Single(); + var composer = Editor.ChildrenOfType().Single(); InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1))); }); @@ -83,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestGridSizeToggling() { AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); - AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); gridSizeIs(4); nextGridSizeIs(8); @@ -99,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); } } diff --git a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs similarity index 65% rename from osu.Game/Rulesets/Edit/GridToolboxGroup.cs rename to osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index b6903c1369..d93b6c27c0 100644 --- a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,35 +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; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; -namespace osu.Game.Rulesets.Edit +namespace osu.Game.Rulesets.Osu.Edit { - public partial class GridToolboxGroup : EditorToolboxGroup + public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { + private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; + + private int currentGridSizeIndex = grid_sizes.Length - 1; + [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; - public GridToolboxGroup() - : base("grid") - { - } - - public BindableFloat StartPositionX { get; } = new BindableFloat(256f) + public BindableFloat StartPositionX { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.X / 2) { MinValue = 0f, - MaxValue = 512f, + MaxValue = OsuPlayfield.BASE_SIZE.X, Precision = 1f }; - public BindableFloat StartPositionY { get; } = new BindableFloat(192) + public BindableFloat StartPositionY { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.Y / 2) { MinValue = 0f, - MaxValue = 384f, + MaxValue = OsuPlayfield.BASE_SIZE.Y, Precision = 1f }; @@ -42,8 +47,8 @@ namespace osu.Game.Rulesets.Edit public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) { - MinValue = -180f, - MaxValue = 180f, + MinValue = -45f, + MaxValue = 45f, Precision = 1f }; @@ -52,6 +57,11 @@ namespace osu.Game.Rulesets.Edit private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; + public OsuGridToolboxGroup() + : base("grid") + { + } + [BackgroundDependencyLoader] private void load() { @@ -74,6 +84,11 @@ namespace osu.Game.Rulesets.Edit Current = GridLinesRotation } }; + + int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); + if (gridSizeIndex >= 0) + currentGridSizeIndex = gridSizeIndex; + updateSpacing(); } protected override void LoadComplete() @@ -104,5 +119,35 @@ namespace osu.Game.Rulesets.Edit gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; }, true); } + + private void nextGridSize() + { + currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; + updateSpacing(); + } + + private void updateSpacing() + { + int gridSize = grid_sizes[currentGridSizeIndex]; + + editorBeatmap.BeatmapInfo.GridSize = gridSize; + Spacing.Value = gridSize; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.EditorCycleGridDisplayMode: + nextGridSize(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e487a5d490..12457fc88d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly GridToolboxGroup GridToolboxGroup = new GridToolboxGroup(); + protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup(); [Cached] protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit { RelativeSizeAxes = Axes.Both }, - rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid + rectangularPositionSnapGrid = new RectangularPositionSnapGrid { RelativeSizeAxes = Axes.Both } @@ -102,16 +102,16 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - GridToolboxGroup.StartPositionX.ValueChanged += x => - rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y); - GridToolboxGroup.StartPositionY.ValueChanged += y => - rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue); - GridToolboxGroup.Spacing.ValueChanged += s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue); - GridToolboxGroup.GridLinesRotation.ValueChanged += r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue; + OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => + rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y), true); + OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => + rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue), true); + OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); RightToolbox.AddRange(new EditorToolboxGroup[] { - GridToolboxGroup, + OsuGridToolboxGroup, new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs deleted file mode 100644 index efc6668ebf..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Input.Bindings; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public partial class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid, IKeyBindingHandler - { - private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; - - private int currentGridSizeIndex = grid_sizes.Length - 1; - - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } = null!; - - public OsuRectangularPositionSnapGrid() - : base(OsuPlayfield.BASE_SIZE / 2) - { - } - - [BackgroundDependencyLoader] - private void load() - { - int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); - if (gridSizeIndex >= 0) - currentGridSizeIndex = gridSizeIndex; - updateSpacing(); - } - - private void nextGridSize() - { - currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - updateSpacing(); - } - - private void updateSpacing() - { - int gridSize = grid_sizes[currentGridSizeIndex]; - - editorBeatmap.BeatmapInfo.GridSize = gridSize; - Spacing = new Vector2(gridSize); - } - - public bool OnPressed(KeyBindingPressEvent e) - { - switch (e.Action) - { - case GlobalAction.EditorCycleGridDisplayMode: - nextGridSize(); - return true; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } -} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index 210af09055..a0042cf605 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -52,9 +52,10 @@ namespace osu.Game.Tests.Visual.Editing { RectangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position) + AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid() { RelativeSizeAxes = Axes.Both, + StartPosition = position, Spacing = spacing, GridLineRotation = rotation }); diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index 4074e681f9..ad00e8d47d 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tournament.Screens.Editors AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); - ScrollContent.Add(grid = new RectangularPositionSnapGrid(Vector2.Zero) + ScrollContent.Add(grid = new RectangularPositionSnapGrid { Spacing = new Vector2(GRID_SPACING), Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 642a125265..3616bc1ca1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -32,9 +32,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - protected LinedPositionSnapGrid(Vector2 startPosition) + protected LinedPositionSnapGrid() { - StartPosition = startPosition; Masking = true; AddLayout(GridCache); diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 930a592850..2392921203 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -42,11 +42,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public RectangularPositionSnapGrid(Vector2 startPosition) - : base(startPosition) - { - } - protected override void CreateContent() { var drawSize = DrawSize; diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index af44641f5e..c98890e294 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -42,11 +42,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public TriangularPositionSnapGrid(Vector2 startPosition) - : base(startPosition) - { - } - private const float sqrt3 = 1.73205080757f; private const float sqrt3_over2 = 0.86602540378f; private const float one_over_sqrt3 = 0.57735026919f; From 351cfbff3e02f96b79eff3a020f0a519e1348052 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:38:10 +0100 Subject: [PATCH 10/33] Fix snapping going out of bounds --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 12457fc88d..3e5cede1d3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; @@ -218,6 +219,10 @@ namespace osu.Game.Rulesets.Osu.Edit { Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); + // A rotated grid can produce a position that is outside of the playfield. + // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. + pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); + result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos); } } From 8ef9bdf861d40eafb12eedbfeb8c093e00c987a2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:39:24 +0100 Subject: [PATCH 11/33] clarify comment --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3e5cede1d3..7b872eef38 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Edit { Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); - // A rotated grid can produce a position that is outside of the playfield. + // A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield. // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); From 040fd5ef9c65ca22aeb5dd9d903d0ed1cf6f0951 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 16:59:12 +0100 Subject: [PATCH 12/33] Add option to change grid type --- .../Edit/OsuGridToolboxGroup.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index d93b6c27c0..892c89ebc2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; @@ -12,6 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.RadioButtons; namespace osu.Game.Rulesets.Osu.Edit { @@ -52,10 +56,13 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + public Bindable GridType { get; } = new Bindable(); + private ExpandableSlider startPositionXSlider = null!; private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; + private EditorRadioButtonCollection gridTypeButtons = null!; public OsuGridToolboxGroup() : base("grid") @@ -82,7 +89,20 @@ namespace osu.Game.Rulesets.Osu.Edit gridLinesRotationSlider = new ExpandableSlider { Current = GridLinesRotation - } + }, + gridTypeButtons = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Square", + () => GridType.Value = PositionSnapGridType.Square, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + new RadioButton("Triangle", + () => GridType.Value = PositionSnapGridType.Triangle, + () => new Triangle()) + } + }, }; int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); @@ -95,6 +115,8 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); + gridTypeButtons.Items.First().Select(); + StartPositionX.BindValueChanged(x => { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; @@ -150,4 +172,10 @@ namespace osu.Game.Rulesets.Osu.Edit { } } + + public enum PositionSnapGridType + { + Square, + Triangle, + } } From 8a331057b0bb76578608c7dd4afc79e98fab82b2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 17:25:17 +0100 Subject: [PATCH 13/33] Make it actually possible to change grid type --- .../Edit/OsuHitObjectComposer.cs | 60 ++++++++--- .../Components/LinedPositionSnapGrid.cs | 97 +---------------- .../Compose/Components/PositionSnapGrid.cs | 102 ++++++++++++++++++ 3 files changed, 152 insertions(+), 107 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 7b872eef38..b0ac8467c1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -84,10 +84,6 @@ namespace osu.Game.Rulesets.Osu.Edit LayerBelowRuleset.AddRange(new Drawable[] { distanceSnapGridContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - rectangularPositionSnapGrid = new RectangularPositionSnapGrid { RelativeSizeAxes = Axes.Both } @@ -103,12 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => - rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y), true); - OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => - rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue), true); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); RightToolbox.AddRange(new EditorToolboxGroup[] { @@ -119,6 +110,49 @@ namespace osu.Game.Rulesets.Osu.Edit ); } + private void updatePositionSnapGrid(ValueChangedEvent obj) + { + if (positionSnapGrid != null) + LayerBelowRuleset.Remove(positionSnapGrid, true); + + switch (obj.NewValue) + { + case PositionSnapGridType.Square: + var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + + positionSnapGrid = rectangularPositionSnapGrid; + break; + + case PositionSnapGridType.Triangle: + var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => triangularPositionSnapGrid.Spacing = s.NewValue, true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => triangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + + positionSnapGrid = triangularPositionSnapGrid; + break; + + default: + throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); + } + + bindPositionSnapGridStartPosition(positionSnapGrid); + positionSnapGrid.RelativeSizeAxes = Axes.Both; + LayerBelowRuleset.Add(positionSnapGrid); + return; + + void bindPositionSnapGridStartPosition(PositionSnapGrid snapGrid) + { + OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => + snapGrid.StartPosition = new Vector2(x.NewValue, snapGrid.StartPosition.Y), true); + OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => + snapGrid.StartPosition = new Vector2(snapGrid.StartPosition.X, y.NewValue), true); + } + } + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); @@ -159,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Cached distanceSnapGridCache = new Cached(); private double? lastDistanceSnapGridTime; - private RectangularPositionSnapGrid rectangularPositionSnapGrid; + private PositionSnapGrid positionSnapGrid; protected override void Update() { @@ -217,13 +251,13 @@ namespace osu.Game.Rulesets.Osu.Edit { if (rectangularGridSnapToggle.Value == TernaryState.True) { - Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); + Vector2 pos = positionSnapGrid.GetSnappedPosition(positionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); // A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield. // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); - result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos); + result.ScreenSpacePosition = positionSnapGrid.ToScreenSpace(pos); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 3616bc1ca1..9ab12e4b71 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -5,61 +5,18 @@ 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.Layout; using osu.Framework.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract partial class LinedPositionSnapGrid : CompositeDrawable + public abstract partial class LinedPositionSnapGrid : PositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - GridCache.Invalidate(); - } - } - - protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - - protected LinedPositionSnapGrid() - { - Masking = true; - - AddLayout(GridCache); - } - - protected override void Update() - { - base.Update(); - - if (!GridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - CreateContent(); - - GridCache.Validate(); - } - } - - protected abstract void CreateContent(); - protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { int index = 0; - var currentPosition = startPosition; + var currentPosition = StartPosition; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; @@ -85,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedLines.Add(gridLine); index += 1; - currentPosition = startPosition + index * step; + currentPosition = StartPosition + index * step; } if (generatedLines.Count == 0) @@ -120,53 +77,5 @@ namespace osu.Game.Screens.Edit.Compose.Components !Precision.AlmostEquals(b, 0) && Math.Sign(a) != Math.Sign(b); } - - protected void GenerateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public abstract Vector2 GetSnappedPosition(Vector2 original); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs new file mode 100644 index 0000000000..dd412e3cd3 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract partial class PositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + GridCache.Invalidate(); + } + } + + protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + protected PositionSnapGrid() + { + Masking = true; + + AddLayout(GridCache); + } + + protected override void Update() + { + base.Update(); + + if (GridCache.IsValid) return; + + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + CreateContent(); + + GridCache.Validate(); + } + + protected abstract void CreateContent(); + + protected void GenerateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public abstract Vector2 GetSnappedPosition(Vector2 original); + } +} From 847f04e63a0fb9b58bcea0ca35ea9d61880a5528 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 17:32:09 +0100 Subject: [PATCH 14/33] reduce opacity of middle cardinal lines --- .../Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 9ab12e4b71..94da9c5b84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (generatedLines.Count == 0) return; - generatedLines.First().Alpha = 0.3f; + generatedLines.First().Alpha = 0.2f; AddRangeInternal(generatedLines); } From d0ca3f2b2b086ddbfae4ed5f9d6e44b471804722 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 18:24:05 +0100 Subject: [PATCH 15/33] Add circular grid --- .../Edit/OsuGridToolboxGroup.cs | 6 +- .../Edit/OsuHitObjectComposer.cs | 8 ++ .../Components/CircularPositionSnapGrid.cs | 106 ++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs 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); + } + } +} From f649fa106f5de94592ff6d2ba55684625451bde3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 00:43:41 +0100 Subject: [PATCH 16/33] Added bindables and binding with BindTo --- .../Editor/TestSceneOsuEditorGrids.cs | 2 +- .../Edit/OsuGridToolboxGroup.cs | 28 +++++++++++ .../Edit/OsuHitObjectComposer.cs | 23 +++------ .../Components/CircularPositionSnapGrid.cs | 37 ++++++-------- .../Components/LinedPositionSnapGrid.cs | 4 +- .../Compose/Components/PositionSnapGrid.cs | 15 ++---- .../Components/RectangularPositionSnapGrid.cs | 46 ++++++----------- .../Components/TriangularPositionSnapGrid.cs | 49 +++++++------------ 8 files changed, 92 insertions(+), 112 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 299db23ccc..ff406b1b88 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 0013f23057..46e43deaae 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; + /// + /// X position of the grid's origin. + /// public BindableFloat StartPositionX { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.X / 2) { MinValue = 0f, @@ -35,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Y position of the grid's origin. + /// public BindableFloat StartPositionY { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.Y / 2) { MinValue = 0f, @@ -42,6 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// The spacing between grid lines. + /// public BindableFloat Spacing { get; } = new BindableFloat(4f) { MinValue = 4f, @@ -49,6 +59,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Rotation of the grid lines in degrees. + /// public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) { MinValue = -45f, @@ -56,6 +69,18 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Read-only bindable representing the grid's origin. + /// Equivalent to new Vector2(StartPositionX, StartPositionY) + /// + public Bindable StartPosition { get; } = new Bindable(); + + /// + /// Read-only bindable representing the grid's spacing in both the X and Y dimension. + /// Equivalent to new Vector2(Spacing) + /// + public Bindable SpacingVector { get; } = new Bindable(); + public Bindable GridType { get; } = new Bindable(); private ExpandableSlider startPositionXSlider = null!; @@ -124,18 +149,21 @@ namespace osu.Game.Rulesets.Osu.Edit { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}"; + StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y); }, true); StartPositionY.BindValueChanged(y => { startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}"; startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}"; + StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue); }, true); Spacing.BindValueChanged(spacing => { spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; + SpacingVector.Value = new Vector2(spacing.NewValue); }, true); GridLinesRotation.BindValueChanged(rotation => diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1e7d07dbdd..84d5adbc52 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -120,8 +120,8 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Square: var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); + rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); positionSnapGrid = rectangularPositionSnapGrid; break; @@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Triangle: var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => triangularPositionSnapGrid.Spacing = s.NewValue, true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => triangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); + triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); positionSnapGrid = triangularPositionSnapGrid; break; @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Circle: var circularPositionSnapGrid = new CircularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => circularPositionSnapGrid.Spacing = s.NewValue, true); + circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); positionSnapGrid = circularPositionSnapGrid; break; @@ -147,18 +147,11 @@ namespace osu.Game.Rulesets.Osu.Edit throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); } - bindPositionSnapGridStartPosition(positionSnapGrid); + // Bind the start position to the toolbox sliders. + positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + positionSnapGrid.RelativeSizeAxes = Axes.Both; LayerBelowRuleset.Add(positionSnapGrid); - return; - - void bindPositionSnapGridStartPosition(PositionSnapGrid snapGrid) - { - OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => - snapGrid.StartPosition = new Vector2(x.NewValue, snapGrid.StartPosition.Y), true); - OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => - snapGrid.StartPosition = new Vector2(snapGrid.StartPosition.X, y.NewValue), true); - } } protected override ComposeBlueprintContainer CreateBlueprintContainer() diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs index b9b0cbe389..403a270359 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs @@ -4,33 +4,28 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; 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 + public BindableFloat Spacing { get; } = new BindableFloat(1f) { - get => spacing; - set - { - if (spacing <= 0) - throw new ArgumentException("Grid spacing must be positive."); + MinValue = 0f, + }; - spacing = value; - GridCache.Invalidate(); - } + public CircularPositionSnapGrid() + { + Spacing.BindValueChanged(_ => GridCache.Invalidate()); } protected override void CreateContent() @@ -39,11 +34,11 @@ namespace osu.Game.Screens.Edit.Compose.Components // 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) + MathF.Max(StartPosition.Value.Length, (StartPosition.Value - drawSize).Length), + MathF.Max((StartPosition.Value - new Vector2(drawSize.X, 0)).Length, (StartPosition.Value - new Vector2(0, drawSize.Y)).Length) ); - generateCircles((int)(maxDist / Spacing) + 1); + generateCircles((int)(maxDist / Spacing.Value) + 1); GenerateOutline(drawSize); } @@ -58,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components 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); + float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing.Value * 2); var gridCircle = new CircularContainer { @@ -69,7 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components RelativeSizeAxes = Axes.None, Width = diameter, Height = diameter, - Position = StartPosition, + Position = StartPosition.Value, Masking = true, Child = new Box { @@ -92,15 +87,15 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = original - StartPosition; + Vector2 relativeToStart = original - StartPosition.Value; if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) - return StartPosition; + return StartPosition.Value; float length = relativeToStart.Length; - float wantedLength = MathF.Round(length / Spacing) * Spacing; + float wantedLength = MathF.Round(length / Spacing.Value) * Spacing.Value; - return StartPosition + Vector2.Multiply(relativeToStart, wantedLength / length); + return StartPosition.Value + Vector2.Multiply(relativeToStart, wantedLength / length); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 94da9c5b84..ebdd76a4e2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { int index = 0; - var currentPosition = StartPosition; + var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedLines.Add(gridLine); index += 1; - currentPosition = StartPosition + index * step; + currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index dd412e3cd3..36687ef73a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,20 +12,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract partial class PositionSnapGrid : CompositeDrawable { - private Vector2 startPosition; - /// /// The position of the origin of this in local coordinates. /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - GridCache.Invalidate(); - } - } + public Bindable StartPosition { get; } = new Bindable(Vector2.Zero); protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); @@ -32,6 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { Masking = true; + StartPosition.BindValueChanged(_ => GridCache.Invalidate()); + AddLayout(GridCache); } diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 2392921203..3bf0ef8ac3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Game.Utils; using osuTK; @@ -9,60 +10,43 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class RectangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 spacing = Vector2.One; - /// /// The spacing between grid lines of this . /// - public Vector2 Spacing - { - get => spacing; - set - { - if (spacing.X <= 0 || spacing.Y <= 0) - throw new ArgumentException("Grid spacing must be positive."); - - spacing = value; - GridCache.Invalidate(); - } - } - - private float gridLineRotation; + public Bindable Spacing { get; } = new Bindable(Vector2.One); /// /// The rotation in degrees of the grid lines of this . /// - public float GridLineRotation + public BindableFloat GridLineRotation { get; } = new BindableFloat(); + + public RectangularPositionSnapGrid() { - get => gridLineRotation; - set - { - gridLineRotation = value; - GridCache.Invalidate(); - } + Spacing.BindValueChanged(_ => GridCache.Invalidate()); + GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); } protected override void CreateContent() { var drawSize = DrawSize; - var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); + var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation.Value)); - GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Value.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Value.Y), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.Value.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(Spacing.Value.X, 0), rot), drawSize); GenerateOutline(drawSize); } public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); - Vector2 offset = Vector2.Divide(relativeToStart, Spacing); + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); + Vector2 offset = Vector2.Divide(relativeToStart, Spacing.Value); Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); - return StartPosition + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing), -GridLineRotation); + return StartPosition.Value + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing.Value), -GridLineRotation.Value); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index c98890e294..93d2c6a74a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Game.Utils; using osuTK; @@ -9,37 +10,23 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid { - private float spacing = 1; - /// /// The spacing between grid lines of this . /// - public float Spacing + public BindableFloat Spacing { get; } = new BindableFloat(1f) { - get => spacing; - set - { - if (spacing <= 0) - throw new ArgumentException("Grid spacing must be positive."); - - spacing = value; - GridCache.Invalidate(); - } - } - - private float gridLineRotation; + MinValue = 0f, + }; /// /// The rotation in degrees of the grid lines of this . /// - public float GridLineRotation + public BindableFloat GridLineRotation { get; } = new BindableFloat(); + + public TriangularPositionSnapGrid() { - get => gridLineRotation; - set - { - gridLineRotation = value; - GridCache.Invalidate(); - } + Spacing.BindValueChanged(_ => GridCache.Invalidate()); + GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); } private const float sqrt3 = 1.73205080757f; @@ -49,10 +36,10 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent() { var drawSize = DrawSize; - float stepSpacing = Spacing * sqrt3_over2; - var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 30); - var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); - var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); + float stepSpacing = Spacing.Value * sqrt3_over2; + var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 30); + var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 90); + var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 150); GenerateGridLines(step1, drawSize); GenerateGridLines(-step1, drawSize); @@ -68,16 +55,16 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); Vector2 hex = pixelToHex(relativeToStart); - return StartPosition + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation); + return StartPosition.Value + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation.Value); } private Vector2 pixelToHex(Vector2 pixel) { - float x = pixel.X / Spacing; - float y = pixel.Y / Spacing; + float x = pixel.X / Spacing.Value; + float y = pixel.Y / Spacing.Value; // Algorithm from Charles Chambers // with modifications and comments by Chris Cox 2023 // @@ -96,7 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Taken from // with modifications for the different definition of size. - return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); + return new Vector2(Spacing.Value * (hex.X - hex.Y / 2), Spacing.Value * one_over_sqrt3 * 1.5f * hex.Y); } } } From 1c75357d77fba3723476d2bd1657e1d1fdd45efd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 01:12:23 +0100 Subject: [PATCH 17/33] fix compile --- osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index ad00e8d47d..a7f0a52003 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -53,13 +53,14 @@ namespace osu.Game.Tournament.Screens.Editors ScrollContent.Add(grid = new RectangularPositionSnapGrid { - Spacing = new Vector2(GRID_SPACING), Anchor = Anchor.Centre, Origin = Anchor.Centre, BypassAutoSizeAxes = Axes.Both, Depth = float.MaxValue }); + grid.Spacing.Value = new Vector2(GRID_SPACING); + LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage(); updateMessage(); } From 9a8c41f6ca8a4e1df4d3f98406126d40858ed419 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 14:32:20 +0100 Subject: [PATCH 18/33] Saving exact grid spacing --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 46e43deaae..442575711a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -133,10 +133,10 @@ namespace osu.Game.Rulesets.Osu.Edit }, }; + Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); if (gridSizeIndex >= 0) currentGridSizeIndex = gridSizeIndex; - updateSpacing(); } protected override void LoadComplete() @@ -164,6 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; SpacingVector.Value = new Vector2(spacing.NewValue); + editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue; }, true); GridLinesRotation.BindValueChanged(rotation => @@ -176,15 +177,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void nextGridSize() { currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - updateSpacing(); - } - - private void updateSpacing() - { - int gridSize = grid_sizes[currentGridSizeIndex]; - - editorBeatmap.BeatmapInfo.GridSize = gridSize; - Spacing.Value = gridSize; + Spacing.Value = grid_sizes[currentGridSizeIndex]; } public bool OnPressed(KeyBindingPressEvent e) From 493e3a5f7a2cf9b355cf8319abe16b12c069f92d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 02:55:47 +0100 Subject: [PATCH 19/33] use G to change grid type --- .../Edit/OsuGridToolboxGroup.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 442575711a..c07e8028b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -22,9 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; + private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - private int currentGridSizeIndex = grid_sizes.Length - 1; + private int currentGridTypeIndex; [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -134,9 +134,6 @@ namespace osu.Game.Rulesets.Osu.Edit }; Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; - int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); - if (gridSizeIndex >= 0) - currentGridSizeIndex = gridSizeIndex; } protected override void LoadComplete() @@ -174,10 +171,11 @@ namespace osu.Game.Rulesets.Osu.Edit }, true); } - private void nextGridSize() + private void nextGridType() { - currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - Spacing.Value = grid_sizes[currentGridSizeIndex]; + currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; + GridType.Value = grid_types[currentGridTypeIndex]; + gridTypeButtons.Items[currentGridTypeIndex].Select(); } public bool OnPressed(KeyBindingPressEvent e) @@ -185,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (e.Action) { case GlobalAction.EditorCycleGridDisplayMode: - nextGridSize(); + nextGridType(); return true; } From e47d570e68d155375833f9a06f71820fabd7af47 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 03:53:42 +0100 Subject: [PATCH 20/33] improve UI --- .../Edit/OsuGridToolboxGroup.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index c07e8028b6..da5849a77a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -6,10 +6,12 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -17,6 +19,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit { @@ -29,6 +32,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; + [Resolved] + private IExpandingContainer? expandingContainer { get; set; } + /// /// X position of the grid's origin. /// @@ -125,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), new RadioButton("Triangle", () => GridType.Value = PositionSnapGridType.Triangle, - () => new Triangle()), + () => new OutlineTriangle(true, 20)), new RadioButton("Circle", () => GridType.Value = PositionSnapGridType.Circle, () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), @@ -136,6 +142,36 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; } + public partial class OutlineTriangle : BufferedContainer + { + public OutlineTriangle(bool outlineOnly, float size) + : base(cachedFrameBuffer: true) + { + Size = new Vector2(size); + + InternalChildren = new Drawable[] + { + new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, + }; + + if (outlineOnly) + { + AddInternal(new EquilateralTriangle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.48f, + Colour = Color4.Black, + Size = new Vector2(size - 7), + Blending = BlendingParameters.None, + }); + } + + Blending = BlendingParameters.Additive; + } + } + protected override void LoadComplete() { base.LoadComplete(); From 904ea2e436bf694f5e95f0bf02358bd6668304b1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 15:48:09 +0100 Subject: [PATCH 21/33] move OutlineTriangle code down --- .../Edit/OsuGridToolboxGroup.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index da5849a77a..72e60a5515 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -142,36 +142,6 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; } - public partial class OutlineTriangle : BufferedContainer - { - public OutlineTriangle(bool outlineOnly, float size) - : base(cachedFrameBuffer: true) - { - Size = new Vector2(size); - - InternalChildren = new Drawable[] - { - new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, - }; - - if (outlineOnly) - { - AddInternal(new EquilateralTriangle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.48f, - Colour = Color4.Black, - Size = new Vector2(size - 7), - Blending = BlendingParameters.None, - }); - } - - Blending = BlendingParameters.Additive; - } - } - protected override void LoadComplete() { base.LoadComplete(); @@ -229,6 +199,36 @@ namespace osu.Game.Rulesets.Osu.Edit public void OnReleased(KeyBindingReleaseEvent e) { } + + public partial class OutlineTriangle : BufferedContainer + { + public OutlineTriangle(bool outlineOnly, float size) + : base(cachedFrameBuffer: true) + { + Size = new Vector2(size); + + InternalChildren = new Drawable[] + { + new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, + }; + + if (outlineOnly) + { + AddInternal(new EquilateralTriangle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.48f, + Colour = Color4.Black, + Size = new Vector2(size - 7), + Blending = BlendingParameters.None, + }); + } + + Blending = BlendingParameters.Additive; + } + } } public enum PositionSnapGridType From 33e559f83502f84f93d328fea61d9e9fc7d83679 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:41:22 +0100 Subject: [PATCH 22/33] add integer keyboard step to sliders --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 72e60a5515..6ed7054159 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -107,19 +107,23 @@ namespace osu.Game.Rulesets.Osu.Edit { startPositionXSlider = new ExpandableSlider { - Current = StartPositionX + Current = StartPositionX, + KeyboardStep = 1, }, startPositionYSlider = new ExpandableSlider { - Current = StartPositionY + Current = StartPositionY, + KeyboardStep = 1, }, spacingSlider = new ExpandableSlider { - Current = Spacing + Current = Spacing, + KeyboardStep = 1, }, gridLinesRotationSlider = new ExpandableSlider { - Current = GridLinesRotation + Current = GridLinesRotation, + KeyboardStep = 1, }, gridTypeButtons = new EditorRadioButtonCollection { From 20e338b8920d33180434ce6c4a7f362118a6fdfe Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:45:21 +0100 Subject: [PATCH 23/33] also hide grid from points button when not hovered --- .../Edit/OsuGridToolboxGroup.cs | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 6ed7054159..981148858d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -125,20 +125,29 @@ namespace osu.Game.Rulesets.Osu.Edit Current = GridLinesRotation, KeyboardStep = 1, }, - gridTypeButtons = new EditorRadioButtonCollection + new FillFlowContainer { RelativeSizeAxes = Axes.X, - Items = new[] + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] { - new RadioButton("Square", - () => GridType.Value = PositionSnapGridType.Square, - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), - new RadioButton("Triangle", - () => GridType.Value = PositionSnapGridType.Triangle, - () => new OutlineTriangle(true, 20)), - new RadioButton("Circle", - () => GridType.Value = PositionSnapGridType.Circle, - () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), + gridTypeButtons = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Square", + () => GridType.Value = PositionSnapGridType.Square, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + new RadioButton("Triangle", + () => GridType.Value = PositionSnapGridType.Triangle, + () => new OutlineTriangle(true, 20)), + new RadioButton("Circle", + () => GridType.Value = PositionSnapGridType.Circle, + () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), + } + }, } }, }; @@ -176,8 +185,14 @@ namespace osu.Game.Rulesets.Osu.Edit GridLinesRotation.BindValueChanged(rotation => { - gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:N0}"; - gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; + gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}"; + gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}"; + }, true); + + expandingContainer?.Expanded.BindValueChanged(v => + { + gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); + gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; }, true); } From 8425c7226c20fcafe961ca4e6c72b3d718dc7105 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:54:04 +0100 Subject: [PATCH 24/33] fix rectangular and triangular grid tests --- .../Editing/TestSceneRectangularPositionSnapGrid.cs | 13 ++++++++----- .../Editing/TestSceneTriangularPositionSnapGrid.cs | 12 ++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index a0042cf605..19903737f6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -52,12 +52,15 @@ namespace osu.Game.Tests.Visual.Editing { RectangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid() + AddStep("create grid", () => { - RelativeSizeAxes = Axes.Both, - StartPosition = position, - Spacing = spacing, - GridLineRotation = rotation + Child = grid = new RectangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + grid.GridLineRotation.Value = rotation; }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs index 2f5ffd8423..b1f82fa114 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs @@ -52,11 +52,15 @@ namespace osu.Game.Tests.Visual.Editing { TriangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new TriangularPositionSnapGrid(position) + AddStep("create grid", () => { - RelativeSizeAxes = Axes.Both, - Spacing = spacing, - GridLineRotation = rotation + Child = grid = new TriangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + grid.GridLineRotation.Value = rotation; }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer From 31d17994807dd4fe36b2cc73b699753884f21d19 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:56:10 +0100 Subject: [PATCH 25/33] Create TestSceneCircularPositionSnapGrid.cs --- .../TestSceneCircularPositionSnapGrid.cs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs new file mode 100644 index 0000000000..4481199c94 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs @@ -0,0 +1,111 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneCircularPositionSnapGrid : OsuManualInputManagerTestScene + { + private Container content; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Gray + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + } + }); + } + + private static readonly object[][] test_cases = + { + new object[] { new Vector2(0, 0), 10, 0f }, + new object[] { new Vector2(240, 180), 10, 10f }, + new object[] { new Vector2(160, 120), 30, -10f }, + new object[] { new Vector2(480, 360), 100, 0f }, + }; + + [TestCaseSource(nameof(test_cases))] + public void TestCircularGrid(Vector2 position, float spacing, float rotation) + { + CircularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new CircularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + private partial class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + } +} From 9796fcff520b52a150cb469c046d13a42a0cc543 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:59:46 +0100 Subject: [PATCH 26/33] Merge position snap grid tests into single file --- .../TestSceneCircularPositionSnapGrid.cs | 111 ----------------- ...apGrid.cs => TestScenePositionSnapGrid.cs} | 51 +++++++- .../TestSceneTriangularPositionSnapGrid.cs | 112 ------------------ 3 files changed, 48 insertions(+), 226 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs rename osu.Game.Tests/Visual/Editing/{TestSceneRectangularPositionSnapGrid.cs => TestScenePositionSnapGrid.cs} (66%) delete mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs deleted file mode 100644 index 4481199c94..0000000000 --- a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Editing -{ - public partial class TestSceneCircularPositionSnapGrid : OsuManualInputManagerTestScene - { - private Container content; - protected override Container Content => content; - - [BackgroundDependencyLoader] - private void load() - { - base.Content.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Gray - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - } - }); - } - - private static readonly object[][] test_cases = - { - new object[] { new Vector2(0, 0), 10, 0f }, - new object[] { new Vector2(240, 180), 10, 10f }, - new object[] { new Vector2(160, 120), 30, -10f }, - new object[] { new Vector2(480, 360), 100, 0f }, - }; - - [TestCaseSource(nameof(test_cases))] - public void TestCircularGrid(Vector2 position, float spacing, float rotation) - { - CircularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new CircularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - private partial class SnappingCursorContainer : CompositeDrawable - { - public Func GetSnapPosition; - - private readonly Drawable cursor; - - public SnappingCursorContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - updatePosition(e.ScreenSpaceMousePosition); - return true; - } - - private void updatePosition(Vector2 screenSpacePosition) - { - cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); - } - } - } -} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs similarity index 66% rename from osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs rename to osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs index 19903737f6..7e66edc2dd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editing { - public partial class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene + public partial class TestScenePositionSnapGrid : OsuManualInputManagerTestScene { private Container content; protected override Container Content => content; @@ -42,8 +42,8 @@ namespace osu.Game.Tests.Visual.Editing private static readonly object[][] test_cases = { new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f }, - new object[] { new Vector2(240, 180), new Vector2(10, 15), 30f }, - new object[] { new Vector2(160, 120), new Vector2(30, 20), -30f }, + new object[] { new Vector2(240, 180), new Vector2(10, 15), 10f }, + new object[] { new Vector2(160, 120), new Vector2(30, 20), -10f }, new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f }, }; @@ -70,6 +70,51 @@ namespace osu.Game.Tests.Visual.Editing })); } + [TestCaseSource(nameof(test_cases))] + public void TestTriangularGrid(Vector2 position, Vector2 spacing, float rotation) + { + TriangularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new TriangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing.X; + grid.GridLineRotation.Value = rotation; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + [TestCaseSource(nameof(test_cases))] + public void TestCircularGrid(Vector2 position, Vector2 spacing, float rotation) + { + CircularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new CircularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing.X; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + private partial class SnappingCursorContainer : CompositeDrawable { public Func GetSnapPosition; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs deleted file mode 100644 index b1f82fa114..0000000000 --- a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Editing -{ - public partial class TestSceneTriangularPositionSnapGrid : OsuManualInputManagerTestScene - { - private Container content; - protected override Container Content => content; - - [BackgroundDependencyLoader] - private void load() - { - base.Content.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Gray - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - } - }); - } - - private static readonly object[][] test_cases = - { - new object[] { new Vector2(0, 0), 10, 0f }, - new object[] { new Vector2(240, 180), 10, 10f }, - new object[] { new Vector2(160, 120), 30, -10f }, - new object[] { new Vector2(480, 360), 100, 0f }, - }; - - [TestCaseSource(nameof(test_cases))] - public void TestTriangularGrid(Vector2 position, float spacing, float rotation) - { - TriangularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new TriangularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing; - grid.GridLineRotation.Value = rotation; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - private partial class SnappingCursorContainer : CompositeDrawable - { - public Func GetSnapPosition; - - private readonly Drawable cursor; - - public SnappingCursorContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - updatePosition(e.ScreenSpaceMousePosition); - return true; - } - - private void updatePosition(Vector2 screenSpacePosition) - { - cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); - } - } - } -} From c5edf4328338ac1ab2ef42f0b8ed004c674aed3a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 19:53:32 +0100 Subject: [PATCH 27/33] fix grid test --- .../Editor/TestSceneOsuEditorGrids.cs | 74 ++++++++++++------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index ff406b1b88..baeb0639d5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -9,6 +10,7 @@ using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; +using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -25,22 +27,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); - rectangularGridActive(false); + gridActive(false); AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any()); - rectangularGridActive(true); + gridActive(true); AddStep("disable distance snap grid", () => InputManager.Key(Key.T)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); - rectangularGridActive(true); + gridActive(true); AddStep("disable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType().Any()); - rectangularGridActive(false); + gridActive(false); } [Test] @@ -58,49 +60,69 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestGridSnapMomentaryToggle() { - rectangularGridActive(false); + gridActive(false); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); - rectangularGridActive(true); + gridActive(true); AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); - rectangularGridActive(false); + gridActive(false); } - private void rectangularGridActive(bool active) + private void gridActive(bool active) where T : PositionSnapGrid { AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); - AddStep("move cursor to (1, 1)", () => + AddStep("move cursor to spacing + (1, 1)", () => { - var composer = Editor.ChildrenOfType().Single(); - InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1))); + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(uniqueSnappingPosition(composer) + new Vector2(1, 1))); }); if (active) - AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(0, 0))); + { + AddAssert("placement blueprint at spacing + (0, 0)", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, + uniqueSnappingPosition(composer)); + }); + } else - AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(1, 1))); + { + AddAssert("placement blueprint at spacing + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, + uniqueSnappingPosition(composer) + new Vector2(1, 1)); + }); + } + } + + private Vector2 uniqueSnappingPosition(PositionSnapGrid grid) + { + return grid switch + { + RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value), + TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), + CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45), + _ => Vector2.Zero + }; } [Test] - public void TestGridSizeToggling() + public void TestGridTypeToggling() { AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridSizeIs(4); + gridActive(true); - nextGridSizeIs(8); - nextGridSizeIs(16); - nextGridSizeIs(32); - nextGridSizeIs(4); + nextGridTypeIs(); + nextGridTypeIs(); + nextGridTypeIs(); } - private void nextGridSizeIs(int size) + private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); - gridSizeIs(size); + AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + gridActive(true); } - - private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) - && EditorBeatmap.BeatmapInfo.GridSize == size); } } From 594b6fe1672ddd4983e06986496519a6661c2352 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 21:57:11 +0100 Subject: [PATCH 28/33] Add back the old keybind for cycling grid spacing --- .../Editor/TestSceneOsuEditorGrids.cs | 25 ++++++++++++++++++- .../Edit/OsuGridToolboxGroup.cs | 10 ++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index baeb0639d5..5636bb51b9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -107,6 +107,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }; } + [Test] + public void TestGridSizeToggling() + { + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + gridSizeIs(4); + + nextGridSizeIs(8); + nextGridSizeIs(16); + nextGridSizeIs(32); + nextGridSizeIs(4); + } + + private void nextGridSizeIs(int size) + { + AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); + gridSizeIs(size); + } + + private void gridSizeIs(int size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) + && EditorBeatmap.BeatmapInfo.GridSize == size); + [Test] public void TestGridTypeToggling() { @@ -121,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); gridActive(true); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 981148858d..237ccf3e58 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -100,6 +100,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + private const float max_automatic_spacing = 64; + [BackgroundDependencyLoader] private void load() { @@ -196,11 +198,9 @@ namespace osu.Game.Rulesets.Osu.Edit }, true); } - private void nextGridType() + private void nextGridSize() { - currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; - GridType.Value = grid_types[currentGridTypeIndex]; - gridTypeButtons.Items[currentGridTypeIndex].Select(); + Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; } public bool OnPressed(KeyBindingPressEvent e) @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (e.Action) { case GlobalAction.EditorCycleGridDisplayMode: - nextGridType(); + nextGridSize(); return true; } From 39f4a1aa8e19a6570c24bef2a71e0c078e7a1049 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 15:34:05 +0100 Subject: [PATCH 29/33] conflict fixes --- .../Editor/TestSceneOsuEditorGrids.cs | 18 ------------------ .../Edit/OsuGridToolboxGroup.cs | 5 ----- 2 files changed, 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 5636bb51b9..21427ba281 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -129,23 +129,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void gridSizeIs(int size) => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); - - [Test] - public void TestGridTypeToggling() - { - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); - AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridActive(true); - - nextGridTypeIs(); - nextGridTypeIs(); - nextGridTypeIs(); - } - - private void nextGridTypeIs() where T : PositionSnapGrid - { - AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); - gridActive(true); - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 237ccf3e58..76e735449a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -25,10 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - - private int currentGridTypeIndex; - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; From de14da95fa6d0230af1aeef7e9b0afd5caaa059e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 15:44:20 +0100 Subject: [PATCH 30/33] Remove other grid types --- .../Editor/TestSceneOsuEditorGrids.cs | 3 - .../Edit/OsuGridToolboxGroup.cs | 79 -------------- .../Edit/OsuHitObjectComposer.cs | 41 ++----- .../Editing/TestScenePositionSnapGrid.cs | 45 -------- .../Components/CircularPositionSnapGrid.cs | 101 ------------------ .../Components/TriangularPositionSnapGrid.cs | 89 --------------- 6 files changed, 7 insertions(+), 351 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs delete mode 100644 osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 21427ba281..7cafd10454 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -101,8 +100,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return grid switch { RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value), - TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), - CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45), _ => Vector2.Zero }; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 76e735449a..e82ca780ad 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,13 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -16,9 +12,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit { @@ -82,13 +76,10 @@ namespace osu.Game.Rulesets.Osu.Edit /// public Bindable SpacingVector { get; } = new Bindable(); - public Bindable GridType { get; } = new Bindable(); - private ExpandableSlider startPositionXSlider = null!; private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; - private EditorRadioButtonCollection gridTypeButtons = null!; public OsuGridToolboxGroup() : base("grid") @@ -122,31 +113,6 @@ namespace osu.Game.Rulesets.Osu.Edit Current = GridLinesRotation, KeyboardStep = 1, }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Children = new Drawable[] - { - gridTypeButtons = new EditorRadioButtonCollection - { - RelativeSizeAxes = Axes.X, - Items = new[] - { - new RadioButton("Square", - () => GridType.Value = PositionSnapGridType.Square, - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), - new RadioButton("Triangle", - () => GridType.Value = PositionSnapGridType.Triangle, - () => new OutlineTriangle(true, 20)), - new RadioButton("Circle", - () => GridType.Value = PositionSnapGridType.Circle, - () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), - } - }, - } - }, }; Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; @@ -156,8 +122,6 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - gridTypeButtons.Items.First().Select(); - StartPositionX.BindValueChanged(x => { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; @@ -185,12 +149,6 @@ namespace osu.Game.Rulesets.Osu.Edit gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}"; gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}"; }, true); - - expandingContainer?.Expanded.BindValueChanged(v => - { - gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); - gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; - }, true); } private void nextGridSize() @@ -213,42 +171,5 @@ namespace osu.Game.Rulesets.Osu.Edit public void OnReleased(KeyBindingReleaseEvent e) { } - - public partial class OutlineTriangle : BufferedContainer - { - public OutlineTriangle(bool outlineOnly, float size) - : base(cachedFrameBuffer: true) - { - Size = new Vector2(size); - - InternalChildren = new Drawable[] - { - new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, - }; - - if (outlineOnly) - { - AddInternal(new EquilateralTriangle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.48f, - Colour = Color4.Black, - Size = new Vector2(size - 7), - Blending = BlendingParameters.None, - }); - } - - Blending = BlendingParameters.Additive; - } - } - } - - public enum PositionSnapGridType - { - Square, - Triangle, - Circle, } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 84d5adbc52..51bb74926f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); + updatePositionSnapGrid(); RightToolbox.AddRange(new EditorToolboxGroup[] { @@ -110,45 +110,18 @@ namespace osu.Game.Rulesets.Osu.Edit ); } - private void updatePositionSnapGrid(ValueChangedEvent obj) + private void updatePositionSnapGrid() { if (positionSnapGrid != null) LayerBelowRuleset.Remove(positionSnapGrid, true); - switch (obj.NewValue) - { - case PositionSnapGridType.Square: - var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); + var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); - rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); - rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); + rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); + rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); - positionSnapGrid = rectangularPositionSnapGrid; - break; - - case PositionSnapGridType.Triangle: - var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); - - triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); - triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); - - positionSnapGrid = triangularPositionSnapGrid; - break; - - case PositionSnapGridType.Circle: - var circularPositionSnapGrid = new CircularPositionSnapGrid(); - - circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); - - positionSnapGrid = circularPositionSnapGrid; - break; - - default: - throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); - } - - // Bind the start position to the toolbox sliders. - positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + positionSnapGrid = rectangularPositionSnapGrid; positionSnapGrid.RelativeSizeAxes = Axes.Both; LayerBelowRuleset.Add(positionSnapGrid); diff --git a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs index 7e66edc2dd..2721bc3602 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs @@ -70,51 +70,6 @@ namespace osu.Game.Tests.Visual.Editing })); } - [TestCaseSource(nameof(test_cases))] - public void TestTriangularGrid(Vector2 position, Vector2 spacing, float rotation) - { - TriangularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new TriangularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing.X; - grid.GridLineRotation.Value = rotation; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - [TestCaseSource(nameof(test_cases))] - public void TestCircularGrid(Vector2 position, Vector2 spacing, float rotation) - { - CircularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new CircularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing.X; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - private partial class SnappingCursorContainer : CompositeDrawable { public Func GetSnapPosition; diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs deleted file mode 100644 index 403a270359..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Utils; -using osuTK; - -namespace osu.Game.Screens.Edit.Compose.Components -{ - public partial class CircularPositionSnapGrid : PositionSnapGrid - { - /// - /// The spacing between grid lines of this . - /// - public BindableFloat Spacing { get; } = new BindableFloat(1f) - { - MinValue = 0f, - }; - - public CircularPositionSnapGrid() - { - Spacing.BindValueChanged(_ => 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.Value.Length, (StartPosition.Value - drawSize).Length), - MathF.Max((StartPosition.Value - new Vector2(drawSize.X, 0)).Length, (StartPosition.Value - new Vector2(0, drawSize.Y)).Length) - ); - - generateCircles((int)(maxDist / Spacing.Value) + 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.Value * 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.Value, - 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.Value; - - if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) - return StartPosition.Value; - - float length = relativeToStart.Length; - float wantedLength = MathF.Round(length / Spacing.Value) * Spacing.Value; - - return StartPosition.Value + Vector2.Multiply(relativeToStart, wantedLength / length); - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs deleted file mode 100644 index 93d2c6a74a..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Bindables; -using osu.Game.Utils; -using osuTK; - -namespace osu.Game.Screens.Edit.Compose.Components -{ - public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid - { - /// - /// The spacing between grid lines of this . - /// - public BindableFloat Spacing { get; } = new BindableFloat(1f) - { - MinValue = 0f, - }; - - /// - /// The rotation in degrees of the grid lines of this . - /// - public BindableFloat GridLineRotation { get; } = new BindableFloat(); - - public TriangularPositionSnapGrid() - { - Spacing.BindValueChanged(_ => GridCache.Invalidate()); - GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); - } - - private const float sqrt3 = 1.73205080757f; - private const float sqrt3_over2 = 0.86602540378f; - private const float one_over_sqrt3 = 0.57735026919f; - - protected override void CreateContent() - { - var drawSize = DrawSize; - float stepSpacing = Spacing.Value * sqrt3_over2; - var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 30); - var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 90); - var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 150); - - GenerateGridLines(step1, drawSize); - GenerateGridLines(-step1, drawSize); - - GenerateGridLines(step2, drawSize); - GenerateGridLines(-step2, drawSize); - - GenerateGridLines(step3, drawSize); - GenerateGridLines(-step3, drawSize); - - GenerateOutline(drawSize); - } - - public override Vector2 GetSnappedPosition(Vector2 original) - { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); - Vector2 hex = pixelToHex(relativeToStart); - - return StartPosition.Value + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation.Value); - } - - private Vector2 pixelToHex(Vector2 pixel) - { - float x = pixel.X / Spacing.Value; - float y = pixel.Y / Spacing.Value; - // Algorithm from Charles Chambers - // with modifications and comments by Chris Cox 2023 - // - float t = sqrt3 * y + 1; // scaled y, plus phase - float temp1 = MathF.Floor(t + x); // (y+x) diagonal, this calc needs floor - float temp2 = t - x; // (y-x) diagonal, no floor needed - float temp3 = 2 * x + 1; // scaled horizontal, no floor needed, needs +1 to get correct phase - float qf = (temp1 + temp3) / 3.0f; // pseudo x with fraction - float rf = (temp1 + temp2) / 3.0f; // pseudo y with fraction - float q = MathF.Floor(qf); // pseudo x, quantized and thus requires floor - float r = MathF.Floor(rf); // pseudo y, quantized and thus requires floor - return new Vector2(q, r); - } - - private Vector2 hexToPixel(Vector2 hex) - { - // Taken from - // with modifications for the different definition of size. - return new Vector2(Spacing.Value * (hex.X - hex.Y / 2), Spacing.Value * one_over_sqrt3 * 1.5f * hex.Y); - } - } -} From 460c584dca79ec4dc40df0f49e6721edcb6e6fa9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 16:21:33 +0100 Subject: [PATCH 31/33] fix code quality --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index e82ca780ad..21cce553b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -21,9 +20,6 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; - [Resolved] - private IExpandingContainer? expandingContainer { get; set; } - /// /// X position of the grid's origin. /// From 1428cbfbc34f29eb869d8867a5d4f76453fd1cfd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 16:56:57 +0100 Subject: [PATCH 32/33] Remove Masking from PositionSnapGrid This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions. --- .../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++--- .../Compose/Components/PositionSnapGrid.cs | 2 - 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index ebdd76a4e2..8a7f6b5344 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components { protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { + if (Precision.AlmostEquals(step, Vector2.Zero)) + return; + int index = 0; - var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; + float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)); List generatedLines = new List(); - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) + while (true) { + Vector2 currentPosition = StartPosition.Value + index++ * step; + + if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) + { + if (!isMovingTowardsBox(currentPosition, step, drawSize)) + break; + + continue; + } + var gridLine = new Box { Colour = Colour4.White, @@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components Origin = Anchor.Centre, RelativeSizeAxes = Axes.None, Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + Height = Vector2.Distance(p1, p2), + Position = (p1 + p2) / 2, + Rotation = rotation, }; generatedLines.Add(gridLine); - - index += 1; - currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) @@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; } - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + /// + /// Determines if the line starting at and going in the direction of + /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does. + /// + /// The start point of the line. + /// The direction of the line. + /// The width and height of the box. + /// The first intersection point. + /// The second intersection point. + /// Whether the line definitely intersects the box. + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2) { - var p2 = lineStart + lineDir; + p1 = Vector2.Zero; + p2 = Vector2.Zero; - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); + if (Precision.AlmostEquals(lineDir.X, 0)) + { + // If the line is vertical, we only need to check if the X coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X)) + return false; - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + p1 = new Vector2(lineStart.X, 0); + p2 = new Vector2(lineStart.X, box.Y); + return true; + } - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + if (Precision.AlmostEquals(lineDir.Y, 0)) + { + // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y)) + return false; - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); + p1 = new Vector2(0, lineStart.Y); + p2 = new Vector2(box.X, lineStart.Y); + return true; + } + + float m = lineDir.Y / lineDir.X; + float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero. + float b = lineStart.Y - m * lineStart.X; + + // Calculate intersection points with the sides of the box. + var p = new List(4); + + if (0 <= b && b <= box.Y) + p.Add(new Vector2(0, b)); + if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X) + p.Add(new Vector2((box.Y - b) * mInv, box.Y)); + if (0 <= m * box.X + b && m * box.X + b <= box.Y) + p.Add(new Vector2(box.X, m * box.X + b)); + if (0 <= -b * mInv && -b * mInv <= box.X) + p.Add(new Vector2(-b * mInv, 0)); + + switch (p.Count) + { + case 4: + // If there are 4 intersection points, the line is a diagonal of the box. + if (m > 0) + { + p1 = Vector2.Zero; + p2 = box; + } + else + { + p1 = new Vector2(0, box.Y); + p2 = new Vector2(box.X, 0); + } + + break; + + case 3: + // If there are 3 intersection points, the line goes through a corner of the box. + if (p[0] == p[1]) + { + p1 = p[0]; + p2 = p[2]; + } + else + { + p1 = p[0]; + p2 = p[1]; + } + + break; + + case 2: + p1 = p[0]; + p2 = p[1]; + + break; + } + + return !Precision.AlmostEquals(p1, p2); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index 36687ef73a..e576ac1e49 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected PositionSnapGrid() { - Masking = true; - StartPosition.BindValueChanged(_ => GridCache.Invalidate()); AddLayout(GridCache); From 497701950d9582c9792f0da3ec883d69079adeb6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 24 May 2024 18:11:28 +0200 Subject: [PATCH 33/33] fix nitpick --- .../Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 8a7f6b5344..79b4fa2841 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -28,7 +28,8 @@ namespace osu.Game.Screens.Edit.Compose.Components while (true) { - Vector2 currentPosition = StartPosition.Value + index++ * step; + Vector2 currentPosition = StartPosition.Value + index * step; + index++; if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) {