Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hexgrid and circular grid to the osu editor #26310

Merged
merged 12 commits into from
Jul 3, 2024
3 changes: 3 additions & 0 deletions osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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;
Expand Down Expand Up @@ -160,6 +161,8 @@ 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
};
}
Expand Down
107 changes: 105 additions & 2 deletions osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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;
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;
using osu.Game.Screens.Edit.Components.RadioButtons;
using osuTK;
using osuTK.Graphics;

namespace osu.Game.Rulesets.Osu.Edit
{
Expand All @@ -20,6 +27,9 @@ public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandle
[Resolved]
private EditorBeatmap editorBeatmap { get; set; } = null!;

[Resolved]
private IExpandingContainer? expandingContainer { get; set; }

/// <summary>
/// X position of the grid's origin.
/// </summary>
Expand Down Expand Up @@ -55,8 +65,8 @@ public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandle
/// </summary>
public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f)
{
MinValue = -45f,
MaxValue = 45f,
MinValue = -180f,
MaxValue = 180f,
Precision = 1f
};

Expand All @@ -72,10 +82,13 @@ public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandle
/// </summary>
public Bindable<Vector2> SpacingVector { get; } = new Bindable<Vector2>();

public Bindable<PositionSnapGridType> GridType { get; } = new Bindable<PositionSnapGridType>();

private ExpandableSlider<float> startPositionXSlider = null!;
private ExpandableSlider<float> startPositionYSlider = null!;
private ExpandableSlider<float> spacingSlider = null!;
private ExpandableSlider<float> gridLinesRotationSlider = null!;
private EditorRadioButtonCollection gridTypeButtons = null!;

public OsuGridToolboxGroup()
: base("grid")
Expand Down Expand Up @@ -109,6 +122,31 @@ private void load()
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;
Expand All @@ -118,6 +156,8 @@ protected override void LoadComplete()
{
base.LoadComplete();

gridTypeButtons.Items.First().Select();

StartPositionX.BindValueChanged(x =>
{
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}";
Expand Down Expand Up @@ -145,6 +185,32 @@ protected override void LoadComplete()
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);

GridType.BindValueChanged(v =>
{
GridLinesRotation.Disabled = v.NewValue == PositionSnapGridType.Circle;

switch (v.NewValue)
{
case PositionSnapGridType.Square:
GridLinesRotation.Value = ((GridLinesRotation.Value + 405) % 90) - 45;
GridLinesRotation.MinValue = -45;
GridLinesRotation.MaxValue = 45;
break;

case PositionSnapGridType.Triangle:
GridLinesRotation.Value = ((GridLinesRotation.Value + 390) % 60) - 30;
GridLinesRotation.MinValue = -30;
GridLinesRotation.MaxValue = 30;
break;
}
}, true);
}

private void nextGridSize()
Expand All @@ -167,5 +233,42 @@ public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> 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,
}
}
41 changes: 34 additions & 7 deletions osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private void load()
// we may be entering the screen with a selection already active
updateDistanceSnapGrid();

updatePositionSnapGrid();
OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true);

RightToolbox.AddRange(new EditorToolboxGroup[]
{
Expand All @@ -114,18 +114,45 @@ private void load()
);
}

private void updatePositionSnapGrid()
private void updatePositionSnapGrid(ValueChangedEvent<PositionSnapGridType> obj)
{
if (positionSnapGrid != null)
LayerBelowRuleset.Remove(positionSnapGrid, true);

var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
switch (obj.NewValue)
{
case PositionSnapGridType.Square:
var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();

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();

rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);
rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);

positionSnapGrid = circularPositionSnapGrid;
break;

default:
throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value.");
OliBomby marked this conversation as resolved.
Show resolved Hide resolved
}

positionSnapGrid = rectangularPositionSnapGrid;
// Bind the start position to the toolbox sliders.
positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);

positionSnapGrid.RelativeSizeAxes = Axes.Both;
LayerBelowRuleset.Add(positionSnapGrid);
Expand Down
45 changes: 45 additions & 0 deletions osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,51 @@ public void TestRectangularGrid(Vector2 position, Vector2 spacing, float rotatio
}));
}

[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<Vector2, Vector2> GetSnapPosition;
Expand Down
7 changes: 6 additions & 1 deletion osu.Game/Graphics/UserInterface/ExpandableSlider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,14 @@ protected override void LoadComplete()
Expanded.BindValueChanged(v =>
{
label.Text = v.NewValue ? expandedLabelText : contractedLabelText;
slider.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
slider.FadeTo(v.NewValue ? Current.Disabled ? 0.3f : 1f : 0f, 500, Easing.OutQuint);
slider.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
}, true);

Current.BindDisabledChanged(disabled =>
{
slider.Alpha = Expanded.Value ? disabled ? 0.3f : 1 : 0f;
});
}
}

Expand Down
Loading
Loading