mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 20:22:55 +08:00
Merge pull request #14805 from bdach/rectangular-snap-grid
Add rectangular snap grid to osu! editor composer
This commit is contained in:
commit
60c9e9f704
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
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.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneOsuEditorGrids : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGridExclusivity()
|
||||||
|
{
|
||||||
|
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
||||||
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
|
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
|
rectangularGridActive(false);
|
||||||
|
|
||||||
|
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
|
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
|
rectangularGridActive(true);
|
||||||
|
|
||||||
|
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
||||||
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
|
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
|
rectangularGridActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rectangularGridActive(bool active)
|
||||||
|
{
|
||||||
|
AddStep("choose placement tool", () => InputManager.Key(Key.Number2));
|
||||||
|
AddStep("move cursor to (1, 1)", () =>
|
||||||
|
{
|
||||||
|
var composer = Editor.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single();
|
||||||
|
InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1)));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (active)
|
||||||
|
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(0, 0)));
|
||||||
|
else
|
||||||
|
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(1, 1)));
|
||||||
|
|
||||||
|
AddStep("choose selection tool", () => InputManager.Key(Key.Number1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGridSizeToggling()
|
||||||
|
{
|
||||||
|
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
|
AddUntilStep("rectangular grid visible", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().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<OsuRectangularPositionSnapGrid>().Single().Spacing == new Vector2(size)
|
||||||
|
&& EditorBeatmap.BeatmapInfo.GridSize == size);
|
||||||
|
}
|
||||||
|
}
|
@ -42,10 +42,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
};
|
};
|
||||||
|
|
||||||
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
||||||
|
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
||||||
|
|
||||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
||||||
{
|
{
|
||||||
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
|
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }),
|
||||||
|
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
|
||||||
});
|
});
|
||||||
|
|
||||||
private BindableList<HitObject> selectedHitObjects;
|
private BindableList<HitObject> selectedHitObjects;
|
||||||
@ -63,6 +65,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
||||||
},
|
},
|
||||||
distanceSnapGridContainer = new Container
|
distanceSnapGridContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
@ -73,7 +79,19 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
||||||
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||||
distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
distanceSnapToggle.ValueChanged += _ =>
|
||||||
|
{
|
||||||
|
updateDistanceSnapGrid();
|
||||||
|
|
||||||
|
if (distanceSnapToggle.Value == TernaryState.True)
|
||||||
|
rectangularGridSnapToggle.Value = TernaryState.False;
|
||||||
|
};
|
||||||
|
|
||||||
|
rectangularGridSnapToggle.ValueChanged += _ =>
|
||||||
|
{
|
||||||
|
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||||
|
distanceSnapToggle.Value = TernaryState.False;
|
||||||
|
};
|
||||||
|
|
||||||
// we may be entering the screen with a selection already active
|
// we may be entering the screen with a selection already active
|
||||||
updateDistanceSnapGrid();
|
updateDistanceSnapGrid();
|
||||||
@ -91,6 +109,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private readonly Cached distanceSnapGridCache = new Cached();
|
private readonly Cached distanceSnapGridCache = new Cached();
|
||||||
private double? lastDistanceSnapGridTime;
|
private double? lastDistanceSnapGridTime;
|
||||||
|
|
||||||
|
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -122,15 +142,21 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (positionSnap.ScreenSpacePosition != screenSpacePosition)
|
if (positionSnap.ScreenSpacePosition != screenSpacePosition)
|
||||||
return positionSnap;
|
return positionSnap;
|
||||||
|
|
||||||
// will be null if distance snap is disabled or not feasible for the current time value.
|
if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||||
if (distanceSnapGrid == null)
|
{
|
||||||
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
|
||||||
|
|
||||||
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||||
|
|
||||||
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||||
|
{
|
||||||
|
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||||
|
return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||||
|
}
|
||||||
|
|
||||||
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
|
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
|
||||||
{
|
{
|
||||||
// check other on-screen objects for snapping/stacking
|
// check other on-screen objects for snapping/stacking
|
||||||
|
69
osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs
Normal file
69
osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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 class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid, IKeyBindingHandler<GlobalAction>
|
||||||
|
{
|
||||||
|
private static readonly int[] grid_sizes = { 4, 8, 16, 32 };
|
||||||
|
|
||||||
|
private int currentGridSizeIndex = grid_sizes.Length - 1;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; }
|
||||||
|
|
||||||
|
public OsuRectangularPositionSnapGrid()
|
||||||
|
: base(OsuPlayfield.BASE_SIZE / 2)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var 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<GlobalAction> e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case GlobalAction.EditorCycleGridDisplayMode:
|
||||||
|
nextGridSize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
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 class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private Container content;
|
||||||
|
protected override Container<Drawable> 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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) },
|
||||||
|
};
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(test_cases))]
|
||||||
|
public void TestRectangularGrid(Vector2 position, Vector2 spacing)
|
||||||
|
{
|
||||||
|
RectangularPositionSnapGrid grid = null;
|
||||||
|
|
||||||
|
AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Spacing = spacing
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SnappingCursorContainer : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Func<Vector2, Vector2> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -75,6 +75,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode),
|
||||||
new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft),
|
new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft),
|
||||||
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
||||||
|
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
||||||
@ -284,6 +285,9 @@ namespace osu.Game.Input.Bindings
|
|||||||
SeekReplayBackward,
|
SeekReplayBackward,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChatFocus))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChatFocus))]
|
||||||
ToggleChatFocus
|
ToggleChatFocus,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))]
|
||||||
|
EditorCycleGridDisplayMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
|
public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Cycle grid display mode"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Hold for HUD"
|
/// "Hold for HUD"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
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 class RectangularPositionSnapGrid : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The position of the origin of this <see cref="RectangularPositionSnapGrid"/> in local coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 StartPosition { get; }
|
||||||
|
|
||||||
|
private Vector2 spacing = Vector2.One;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spacing between grid lines of this <see cref="RectangularPositionSnapGrid"/>.
|
||||||
|
/// </summary>
|
||||||
|
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 readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
|
||||||
|
|
||||||
|
public RectangularPositionSnapGrid(Vector2 startPosition)
|
||||||
|
{
|
||||||
|
StartPosition = startPosition;
|
||||||
|
|
||||||
|
AddLayout(gridCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!gridCache.IsValid)
|
||||||
|
{
|
||||||
|
ClearInternal();
|
||||||
|
createContent();
|
||||||
|
gridCache.Validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createContent()
|
||||||
|
{
|
||||||
|
var drawSize = DrawSize;
|
||||||
|
|
||||||
|
generateGridLines(Direction.Horizontal, StartPosition.Y, 0, -Spacing.Y);
|
||||||
|
generateGridLines(Direction.Horizontal, StartPosition.Y, drawSize.Y, Spacing.Y);
|
||||||
|
|
||||||
|
generateGridLines(Direction.Vertical, StartPosition.X, 0, -Spacing.X);
|
||||||
|
generateGridLines(Direction.Vertical, StartPosition.X, drawSize.X, Spacing.X);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateGridLines(Direction direction, float startPosition, float endPosition, float step)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
float currentPosition = startPosition;
|
||||||
|
|
||||||
|
while ((endPosition - currentPosition) * Math.Sign(step) > 0)
|
||||||
|
{
|
||||||
|
var gridLine = new Box
|
||||||
|
{
|
||||||
|
Colour = Colour4.White,
|
||||||
|
Alpha = index == 0 ? 0.3f : 0.1f,
|
||||||
|
EdgeSmoothness = new Vector2(0.2f)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (direction == Direction.Horizontal)
|
||||||
|
{
|
||||||
|
gridLine.RelativeSizeAxes = Axes.X;
|
||||||
|
gridLine.Height = 1;
|
||||||
|
gridLine.Y = currentPosition;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gridLine.RelativeSizeAxes = Axes.Y;
|
||||||
|
gridLine.Width = 1;
|
||||||
|
gridLine.X = currentPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddInternal(gridLine);
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
currentPosition = startPosition + index * step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 GetSnappedPosition(Vector2 original)
|
||||||
|
{
|
||||||
|
Vector2 relativeToStart = original - StartPosition;
|
||||||
|
Vector2 offset = Vector2.Divide(relativeToStart, Spacing);
|
||||||
|
Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y));
|
||||||
|
|
||||||
|
return StartPosition + Vector2.Multiply(roundedOffset, Spacing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user