From feadf7a56e0ace3f727e43212312ea0d47c022ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 15:28:14 +0200 Subject: [PATCH 1/2] Allow modifying hold note start/end time via mania composer playfield --- .../Editor/TestSceneManiaHitObjectComposer.cs | 4 +- .../Components/EditHoldNoteEndPiece.cs | 81 +++++++++++++++++++ .../Blueprints/HoldNoteSelectionBlueprint.cs | 58 +++++++++++-- 3 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 8e0b51dcf8..cb0fc72a34 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -186,8 +186,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft)); AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft)); - AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); - AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); + AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); + AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); } private void setScrollStep(ScrollingDirection direction) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs new file mode 100644 index 0000000000..0aa72c28b8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs @@ -0,0 +1,81 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Skinning.Default; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components +{ + public partial class EditHoldNoteEndPiece : CompositeDrawable + { + public Action? DragStarted { get; init; } + public Action? Dragging { get; init; } + public Action? DragEnded { get; init; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + Height = DefaultNotePiece.NOTE_HEIGHT; + + CornerRadius = 5; + Masking = true; + + InternalChild = new DefaultNotePiece(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + DragStarted?.Invoke(); + return true; + } + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + Dragging?.Invoke(e.ScreenSpaceMousePosition); + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + DragEnded?.Invoke(); + } + + private void updateState() + { + var colour = colours.Yellow; + + if (IsHovered) + colour = colour.Lighten(1); + + Colour = colour; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 8ec5213d5f..b8e6aa26a0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -1,16 +1,16 @@ // 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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Screens.Edit; using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints @@ -18,10 +18,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public partial class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - private EditNotePiece head; - private EditNotePiece tail; + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved] + private EditorBeatmap? editorBeatmap { get; set; } + + [Resolved] + private IPositionSnapProvider? positionSnapProvider { get; set; } + + private EditHoldNoteEndPiece head = null!; + private EditHoldNoteEndPiece tail = null!; public HoldNoteSelectionBlueprint(HoldNote hold) : base(hold) @@ -33,8 +42,43 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { InternalChildren = new Drawable[] { - head = new EditNotePiece { RelativeSizeAxes = Axes.X }, - tail = new EditNotePiece { RelativeSizeAxes = Axes.X }, + head = new EditHoldNoteEndPiece + { + RelativeSizeAxes = Axes.X, + DragStarted = () => changeHandler?.BeginChange(), + Dragging = pos => + { + double endTimeBeforeDrag = HitObject.EndTime; + double proposedStartTime = positionSnapProvider?.FindSnappedPositionAndTime(pos).Time ?? HitObjectContainer.TimeAtScreenSpacePosition(pos); + double proposedEndTime = endTimeBeforeDrag; + + if (proposedStartTime >= proposedEndTime) + return; + + HitObject.StartTime = proposedStartTime; + HitObject.EndTime = proposedEndTime; + editorBeatmap?.Update(HitObject); + }, + DragEnded = () => changeHandler?.EndChange(), + }, + tail = new EditHoldNoteEndPiece + { + RelativeSizeAxes = Axes.X, + DragStarted = () => changeHandler?.BeginChange(), + Dragging = pos => + { + double proposedStartTime = HitObject.StartTime; + double proposedEndTime = positionSnapProvider?.FindSnappedPositionAndTime(pos).Time ?? HitObjectContainer.TimeAtScreenSpacePosition(pos); + + if (proposedStartTime >= proposedEndTime) + return; + + HitObject.StartTime = proposedStartTime; + HitObject.EndTime = proposedEndTime; + editorBeatmap?.Update(HitObject); + }, + DragEnded = () => changeHandler?.EndChange(), + }, new Container { RelativeSizeAxes = Axes.Both, From 6d2f9108132b45c07fcdb48aa7bc1a37f879c32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 15:43:33 +0200 Subject: [PATCH 2/2] Add test coverage --- .../Editor/TestSceneManiaHitObjectComposer.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index cb0fc72a34..d88f488582 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -190,6 +190,104 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); } + [Test] + public void TestDragHoldNoteHead() + { + setScrollStep(ScrollingDirection.Down); + + HoldNote holdNote = null; + AddStep("setup beatmap", () => + { + composer.EditorBeatmap.Clear(); + composer.EditorBeatmap.Add(holdNote = new HoldNote + { + Column = 1, + StartTime = 250, + EndTime = 750, + }); + }); + + DrawableHoldNote drawableHoldNote = null; + EditHoldNoteEndPiece headPiece = null; + + AddStep("select blueprint", () => + { + drawableHoldNote = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(drawableHoldNote); + InputManager.Click(MouseButton.Left); + }); + AddStep("grab hold note head", () => + { + headPiece = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(headPiece); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("drag head downwards", () => + { + InputManager.MoveMouseTo(headPiece, new Vector2(0, 100)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("start time moved back", () => holdNote!.StartTime, () => Is.LessThan(250)); + AddAssert("end time unchanged", () => holdNote.EndTime, () => Is.EqualTo(750)); + + AddAssert("head note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.BottomLeft, drawableHoldNote.Head.ScreenSpaceDrawQuad.BottomLeft)); + AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.TopLeft, drawableHoldNote.Tail.ScreenSpaceDrawQuad.BottomLeft)); + + AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == drawableHoldNote.Head.DrawPosition); + AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == drawableHoldNote.Tail.DrawPosition); + } + + [Test] + public void TestDragHoldNoteTail() + { + setScrollStep(ScrollingDirection.Down); + + HoldNote holdNote = null; + AddStep("setup beatmap", () => + { + composer.EditorBeatmap.Clear(); + composer.EditorBeatmap.Add(holdNote = new HoldNote + { + Column = 1, + StartTime = 250, + EndTime = 750, + }); + }); + + DrawableHoldNote drawableHoldNote = null; + EditHoldNoteEndPiece tailPiece = null; + + AddStep("select blueprint", () => + { + drawableHoldNote = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(drawableHoldNote); + InputManager.Click(MouseButton.Left); + }); + AddStep("grab hold note tail", () => + { + tailPiece = this.ChildrenOfType().Last(); + InputManager.MoveMouseTo(tailPiece); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("drag tail upwards", () => + { + InputManager.MoveMouseTo(tailPiece, new Vector2(0, -100)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("start time unchanged", () => holdNote!.StartTime, () => Is.EqualTo(250)); + AddAssert("end time moved forward", () => holdNote.EndTime, () => Is.GreaterThan(750)); + + AddAssert("head note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.BottomLeft, drawableHoldNote.Head.ScreenSpaceDrawQuad.BottomLeft)); + AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.TopLeft, drawableHoldNote.Tail.ScreenSpaceDrawQuad.BottomLeft)); + + AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == drawableHoldNote.Head.DrawPosition); + AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == drawableHoldNote.Tail.DrawPosition); + } + private void setScrollStep(ScrollingDirection direction) => AddStep($"set scroll direction = {direction}", () => ((Bindable)composer.Composer.ScrollingInfo.Direction).Value = direction);