diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs new file mode 100644 index 0000000000..b48f579ec0 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs @@ -0,0 +1,96 @@ +// 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 NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Mania.Tests.Editor +{ + public partial class TestSceneManiaSelectionHandler : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestHorizontalFlipOverSelection() + { + ManiaHitObject first = null!, second = null!, third = null!; + + AddStep("create objects", () => + { + EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 }); + EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 }); + EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 }); + }); + + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("flip horizontally over selection", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("first object stayed in place", () => first.Column, () => Is.EqualTo(2)); + AddAssert("second object flipped", () => second.Column, () => Is.EqualTo(3)); + AddAssert("third object flipped", () => third.Column, () => Is.EqualTo(1)); + } + + [Test] + public void TestHorizontalFlipOverPlayfield() + { + ManiaHitObject first = null!, second = null!, third = null!; + + AddStep("create objects", () => + { + EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 }); + EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 }); + EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 }); + }); + + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("flip horizontally", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.H); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("first object flipped", () => first.Column, () => Is.EqualTo(1)); + AddAssert("second object flipped", () => second.Column, () => Is.EqualTo(2)); + AddAssert("third object flipped", () => third.Column, () => Is.EqualTo(0)); + } + + [Test] + public void TestVerticalFlip() + { + ManiaHitObject first = null!, second = null!, third = null!; + + AddStep("create objects", () => + { + EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 }); + EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 }); + EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 }); + }); + + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("flip vertically", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.J); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("first object flipped", () => first.StartTime, () => Is.EqualTo(2250)); + AddAssert("second object flipped", () => second.StartTime, () => Is.EqualTo(250)); + AddAssert("third object flipped", () => third.StartTime, () => Is.EqualTo(1250)); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 8fdbada04f..9ae2112b30 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; @@ -16,6 +17,16 @@ namespace osu.Game.Rulesets.Mania.Edit [Resolved] private HitObjectComposer composer { get; set; } = null!; + protected override void OnSelectionChanged() + { + base.OnSelectionChanged(); + + var selectedObjects = SelectedItems.OfType().ToArray(); + + SelectionBox.CanFlipX = canFlipX(selectedObjects); + SelectionBox.CanFlipY = canFlipY(selectedObjects); + } + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint; @@ -26,6 +37,58 @@ namespace osu.Game.Rulesets.Mania.Edit return true; } + public override bool HandleFlip(Direction direction, bool flipOverOrigin) + { + var selectedObjects = SelectedItems.OfType().ToArray(); + var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield; + + if (selectedObjects.Length == 0) + return false; + + switch (direction) + { + case Direction.Horizontal: + if (!canFlipX(selectedObjects)) + return false; + + int firstColumn = flipOverOrigin ? 0 : selectedObjects.Min(ho => ho.Column); + int lastColumn = flipOverOrigin ? (int)EditorBeatmap.BeatmapInfo.Difficulty.CircleSize - 1 : selectedObjects.Max(ho => ho.Column); + + EditorBeatmap.PerformOnSelection(hitObject => + { + var maniaObject = (ManiaHitObject)hitObject; + maniaPlayfield.Remove(maniaObject); + maniaObject.Column = firstColumn + (lastColumn - maniaObject.Column); + maniaPlayfield.Add(maniaObject); + }); + + return true; + + case Direction.Vertical: + if (!canFlipY(selectedObjects)) + return false; + + double selectionStartTime = selectedObjects.Min(ho => ho.StartTime); + double selectionEndTime = selectedObjects.Max(ho => ho.GetEndTime()); + + EditorBeatmap.PerformOnSelection(hitObject => + { + hitObject.StartTime = selectionStartTime + (selectionEndTime - hitObject.GetEndTime()); + }); + + return true; + + default: + throw new ArgumentOutOfRangeException(nameof(direction), direction, "Cannot flip over the supplied direction."); + } + } + + private static bool canFlipX(ManiaHitObject[] selectedObjects) + => selectedObjects.Select(ho => ho.Column).Distinct().Count() > 1; + + private static bool canFlipY(ManiaHitObject[] selectedObjects) + => selectedObjects.Length > 1 && selectedObjects.Min(ho => ho.StartTime) < selectedObjects.Max(ho => ho.GetEndTime()); + private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;